rust:什么是所有权
什么是所有权
所有权是用于控制Rust程序如何管理内存的一种规则。所有程序都必须管理他们在运行时使用的内存,有些语言有垃圾回收机制,自动查找不再使用的内存,并将其释放。Rust使用所有权对内存进行管理。所有权的任何功能都不能在程序运行时减慢速度。
所有权规则
- 每个值都有一个所有者
- 一个值在同一时间只能有一个所有者
- 当所有者离开作用域时,值被自动清理
变量作用域
作用域 就是一个变量“能被使用”的代码范围。
use std::io;fn main(){{ let s = String::from("hello"); println!("{}", s); } println!("{}",s);
}
在{}内作为一个作用域,s变量只在{}内有效,出了{}就不再有效,代码块内部的输出语句输出hello,代码块外部的输出语句已经离开作用域,不再有效。
我们已经见过字符串字面量了,就是那些直接写在代码里的字符串值。这种写法虽然方便,但并不是所有场景都适用。
一方面,字符串字面量是 “固定不变” 的,一旦写死就改不了了。另一方面,写代码的时候,我们不可能知道所有要用到的字符串。比如,想获取用户输入的内容并保存,这时候就没法用字面量了。针对这些情况,Rust 提供了第二种字符串类型 ——String。它能把数据存在堆内存里,所以就算编译时不知道具体要存多少文本,也能灵活应对。你可以用 from 函数,从字符串字面量创建出 String 类型的数据。
let s = String::from("hello");
双冒号 :: 运算符允许我们将此特定 from 命名空间 函数,而不是使用某种名称。
这种字符串可以编译,如下所示代码
let mut s = String::from("hello");s.push_str(", world!"); println!("{s}");
push将一个字符串追加到已有的字符串,所以输出是hello,world!
内存和分配
咱们可以把字符串文字(比如代码里直接写的 "hello")和程序的关系,类比成 “印刷好的书” 和 “临时笔记” 的区别:
字符串文字就像书里印好的固定内容。比如你写代码时写了 let s = "你好",这个 "你好" 在程序编译的时候(相当于印刷厂印书的时候)就已经确定了 —— 内容不会变,长度也固定。所以编译器会直接把它 “刻” 进最终的程序文件里(就像文字印在书页上)。等程序运行时(相当于你翻开书看),直接就能从程序里找到这段文字,不用临时找地方存,也不用操心它会不会变,所以速度特别快、效率很高。这一切的前提是:它从出生到结束都不会变(不变性),所以才能这么 “省心” 地提前安排好。但不是所有文字都能这么处理。比如用户在键盘上输入的内容(你写程序时根本不知道用户会输入啥,可能是 10 个字,也可能是 1000 个字),或者程序运行中需要动态改的文字(比如把 "hello" 改成 "hello world",长度变了)。这些文字在编译时(印书时)根本确定不了 —— 既不知道具体内容,也不知道会占多大地方,甚至运行时还会变。这种情况就没法提前 “印” 进程序里了,只能在程序运行时,临时向电脑申请一块内存来存(就像你拿张白纸临时记笔记,内容多少不确定,还能随时涂改)。这就是为什么 Rust 里有 String 这种类型,专门用来处理这种 “动态变化” 的文本。
对于 String 类型,为了支持可变、可增长的文本片段,我们需要在堆上分配一定量的内存(在编译时未知)来保存内容。这意味着:
- 必须在运行时请求内存
- 在完成String后,将此内存释放
当s超出范围时,我们可以把String所需的内存返回给内存分配器。当变量超出作用域时,Rust为我们调用一个特殊的函数,这个函数叫做drop,Rust在}时会自动调用drop。
变量和数据的交互
在 Rust 中,多个变量可以以不同的方式与相同的数据进行交互。
整型
let x = 10;
let y = x;
将10赋值给x变量,将x变量的值赋值给y变量,因为整数是具有已知的固定大小的简单值,这两个 5 值被推送到堆栈上。
String类型
let s1 = String::from("hello");let s2 = s1;
将“hello”赋值给s1,再将s1的值赋值给s2,

字符串由指针、长度、容量三个部分组成。这组数据存储在堆栈上,右侧是堆上保存内容的内存。

当一个变量超出范围时,Rust 会自动调用 drop 函数并清理该变量的堆内存。该图显示了指向同一位置的两个数据指针。当 s2 和 s1 超出范围时,它们都会尝试释放同一个内存。这被称为双重自由错误,是内存安全错误之一。释放内存两次可能会导致内存损坏,从而可能导致安全漏洞。
为了保证内存安全,在 let s2 = s1; 之后,Rust 认为 s1 不再有效。因此,当 s1 超出范围时,Rust 不需要释放任何内容。
fn main(){let s1 = String.from("hello");let s2 = s1;println!("{s1},world!");
}
当程序这样写,调用s1时会出现以下错误,因为 Rust 阻止你使用无效的引用:
cargo runCompiling guessing_game v0.1.0 (D:\desktop\Rust代码\guessing_game\guessing_game)
error[E0382]: borrow of moved value: `s1`--> src\main.rs:40:16|
38 | let s1 = String::from("hello");| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
39 | let s2 = s1;| -- value moved here
40 | println!("{s1},world!");| ^^ value borrowed here after move|= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable|
39 | let s2 = s1.clone();| ++++++++For more information about this error, try `rustc --explain E0382`.
error: could not compile `guessing_game` (bin "guessing_game") due to 1 previous error; 4 warnings emitted
范围和分配
可以把变量想象成一个 “专属储物盒”,盒子里装着数据(内存里的内容),而 “所有权” 就是 “这个盒子归谁管” 的凭证 —— 每个盒子同一时间只归一个变量管。
当你给一个变量赋新值时,相当于 “把盒子里原来的东西拿出来,换成新东西”。这时候,原来的东西因为没人管了(变量的所有权已经转到新值上了),Rust 会立刻叫 “清洁工”(drop函数)来把原来的东西清走,释放它占的空间。
fn main() {let mut s = String::from("hello");s = String::from("rust");println!("{s}");
}

我们声明一个变量 s 并将其绑定到一个 String,其值为 “hello”。然后我们立即创建一个值为 “world” 的新字符串并将其分配给 s

因此,原始字符串立即超出范围。Rust 将运行drop函数,它的内存将立即被释放。当我们打印值时 最后,它将是 rust
变量和数据的克隆
如果想深度赋值String的堆数据,而不是仅仅复制堆栈数据,我们可以用clone方法
nT-1761749942215)]
因此,原始字符串立即超出范围。Rust 将运行drop函数,它的内存将立即被释放。当我们打印值时 最后,它将是 rust
变量和数据的克隆
如果想深度赋值String的堆数据,而不是仅仅复制堆栈数据,我们可以用clone方法
