所有权
所有权(Ownership)
所有运行的程序都必须管理其使用计算机内存的方式。一些语言中具有垃圾回收机制,在程序运行时不断地寻找不再使用的内存;在另一些语言中,程序员必须亲自分配和释放内存。
int* foo() {
int a; // 变量a的作用域开始
a = 100;
char *c = "xyz"; // 变量c的作用域开始
return &a;
} // 变量a和c的作用域结束
变量
首先,让我们看一下所有权的规则。当我们通过举例说明时,请谨记这些规则:
Rust 中的每一个值都有一个被称为其 所有者(owner)的变量。- 值在任一时刻有且只有一个所有者。
- 当所有者(变量)离开作用域,这个值将被丢弃。
绑定(Binding)
首先必须强调下,准确地说
{
let x: i32; // 标识符x, 没有绑定任何资源
let y: i32 = 100; // 标识符y,绑定资源100
}
在如下的
{
let a: i32;
println!("{}", a);
}
上面定义了一个
error: use of possibly uninitialized variable: a
这是因为
{
let a: i32;
a = 100; //必须初始化a
println!("{}", a);
}
其实,
作用域
像
{
{
let a: i32 = 100;
}
println!("{}", a);
}
编译后会得到如下
b.rs:3:20: 3:21 error: unresolved name a [E0425] b.rs:3 println!("{}", a);
像
内存与分配
字符串字面值,即被硬编码进程序里的字符串值;字符串字面值是很方便的,不过它们并不适合使用文本的每一种场景。原因之一就是它们是不可变的。另一个原因是并非所有字符串的值都能在编写代码时就知道。而
let s = String::from("hello");
let mut s = String::from("hello");
s.push_str(", world!"); // push_str() 在字符串后追加字面值
println!("{}", s); // 将打印 `hello, world!`
就字符串字面值来说,我们在编译时就知道其内容,所以文本被直接硬编码进最终的可执行文件中。这使得字符串字面值快速且高效。不过这些特性都只得益于字符串字面值的不可变性。不幸的是,我们不能为了每一个在编译时大小未知的文本而将一块内存放入二进制文件中,并且它的大小还可能随着程序运行而改变。
对于
- 必须在运行时向操作系统请求内存。
- 需要一个当我们处理完
String
时将内存返回给操作系统的方法。
第一部分由我们完成:当调用
{
let s = String::from("hello"); // 从此处起,s 是有效的
// 使用 s
} // 此作用域已结束,
// s 不再有效
这是一个将
移动语义(move)
let x = 5;
let y = x;
我们大致可以猜到这在干什么
所有权转移
在let
关键字完成,和绑定不同的是,=
两边的左值和右值均为两个标识符:
语法:
let 标识符A = 标识符B; // 把“B”绑定资源的所有权转移给“A”
Before move:
a <=> 内存( 地址:A,内容: “xyz”) After move: ab <=> 内存( 地址:A,内容: “xyz”)
被error: use of moved value
。这里有些人可能会疑问,move
,同一时刻只有一个owner
,所以该资源的内存也只会被free
一次。通过这个机制,就保证了内存安全。
let x: T = something;
let y = x;
- 类型
T
没有实现Copy
特性:x
所有权转移到y
。 - 类型
T
实现了Copy
特性:拷贝x
所绑定的资源
为新资源
,并把新资源
的所有权绑定给y
,x
依然拥有原资源的所有权。
譬如如下的代码运行就会抛出异常:
{
let s1: String = String::from("hello");
let s2 = s1;
println!("{}", s1);
}
编译后会得到如下的报错:
c.rs:4:20: 4:21 error: use of moved value:
s1
[E0382] c.rs:4 println!("{}", s1);
错误的意思是在 println
中访问了被 moved
的变量 s1
。

长度表示

如果

之前我们提到过当变量离开作用域后,
如果你在其他语言中听说过术语 浅拷贝(shallow copy)和 深拷贝(deep copy

这里还隐含了一个设计选择:
move 关键字
fn main() {
let x: i32 = 100;
let some_closure = move |i: i32| i + x;
let y = some_closure(2);
println!("x={}, y={}", x, y);
}
// 结果:x=100, y=102
上例中使不使用
fn main() {
let mut x: String = String::from("abc");
let mut some_closure = move |c: char| x.push(c);
let y = some_closure('d');
println!("x={:?}", x);
}
报错:error: use of moved value: x [E0382]
:5 println!("x={:?}", x);
这是因为
fn main() {
let mut x: String = String::from("abc");
{
let mut some_closure = |c: char| x.push(c);
some_closure('d');
}
println!("x={:?}", x); //成功打印:x="abcd"
}
我们只是去掉了x
进行了可变借用,而不是剥夺 x
的所有权,细心的同学还注意到我们在前后还加了 {}
大括号作用域,是为了作用域结束后让可变借用失效,这样 println
才可以成功访问并打印我们期待的内容。
克隆(Clone)
如果我们 确实 需要深度复制
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
Before move:
a <=> 内存(地址:A,内容:100)
After move:
a <=> 内存(地址:A,内容:100)
b <=> 内存(地址:B,内容:100)
浅拷贝与深拷贝
很多面向对象编程语言中“浅拷贝”和“深拷贝”的区别类似。对于基本数据类型来说String
的”深拷贝“怎么办? 可以直接调用String
的
{
let a: String = String::from("xyz");
let b = a.clone(); // <-注意此处的clone
println!("{}", a);
}
这个时候可以编译通过,并且成功打印
Before move:
a <=> 内存( 地址:A,内容: “xyz”) After move:a <=> 内存( 地址:A,内容: “xyz”)b <=> 内存( 地址:B,内容: “xyz”)
注意,然后
所有权与函数
将值传递给函数在语义上与给变量赋值相似。向函数传递值可能会移动或者复制,就像赋值语句一样。
fn main() {
let s = String::from("hello"); // s 进入作用域
takes_ownership(s); // s 的值移动到函数里 ...
// ... 所以到这里不再有效
let x = 5; // x 进入作用域
makes_copy(x); // x 应该移动函数里,
// 但 i32 是 Copy 的,所以在后面可继续使用 x
} // 这里, x 先移出了作用域,然后是 s。但因为 s 的值已被移走,
// 所以不会有特殊操作
fn takes_ownership(some_string: String) { // some_string 进入作用域
println!("{}", some_string);
} // 这里,some_string 移出作用域并调用 `drop` 方法。占用的内存被释放
fn makes_copy(some_integer: i32) { // some_integer 进入作用域
println!("{}", some_integer);
} // 这里,some_integer 移出作用域。不会有特殊操作
当尝试在调用
返回值与作用域
返回值也可以转移所有权。
fn main() {
let s1 = gives_ownership(); // gives_ownership 将返回值
// 移给 s1
let s2 = String::from("hello"); // s2 进入作用域
let s3 = takes_and_gives_back(s2); // s2 被移动到
// takes_and_gives_back 中,
// 它也将返回值移给 s3
} // 这里, s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,
// 所以什么也不会发生。s1 移出作用域并被丢弃
fn gives_ownership() -> String { // gives_ownership 将返回值移动给
// 调用它的函数
let some_string = String::from("hello"); // some_string 进入作用域.
some_string // 返回 some_string 并移出给调用的函数
}
// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域
a_string // 返回 a_string 并移出给调用的函数
}
变量的所有权总是遵循相同的模式:将值赋给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将通过
fn main() {
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
println!("The length of '{}' is {}.", s2, len);
}
fn calculate_length(s: String) -> (String, usize) {
let length = s.len(); // len() 返回字符串的长度
(s, length)
}