所有权

所有权(系统)是 Rust 最为与众不同的特性。
它让 Rust 无需垃圾回收(garbage collector)即可保障内存安全。
所有权以及相关功能:借用(borrowing)、slice 以及 Rust 如何在内存中布局数据。

所有程序都必须管理其运行时使用计算机内存的方式。
一些语言中具有垃圾回收机制,如: java、python;
在另一些语言中,程序员必须亲自分配和释放内存,如:C/C++;

Rust 则选择了第三种方式:通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。

内存与分配

Rust 的所有权围绕着内存分配进行,Rust 对内存管理通过其所有权展开。

它是一种 后进先出 的机制,类似我们日常的落盘子,只能一个一个向上方,然后从最上面拿一个盘子。
一个变量要放到栈上,那么它的大小在编译时就要明确。i32 类型的变量,它就占用 4 个字节。Rust 中可以放到栈上的数据类型,他们的大小都是固定的。
如果是字符串,在运行时才会赋值的变量,在编译期的时候大小是未知或不确定的。所以字符串类型存储在堆上

用于编译时大小未知或不确定的,只有运行时才能确定的数据。在堆上存储一些动态类型的数据。堆是不受系统管理的,是用户自己管理的,也增加了内存溢出的风险。

1.所有权规则

记住这三句话,整个所有权就是围绕这三句话,这三句话也直接概括了所有权。

  1. Rust 中的每一个值都有一个所有者(owner)
  2. 值在任一时刻有且只有一个所有者。
  3. 当所有者(变量)离开作用域,这个值将被丢弃。

2.变量作用域

大部份编程语言都有 作用域(scope) 的概念,但是在rust中,这个概念被提到一个很重要的高度。
先看看rust一些变量的 作用域(scope)
作用域是一个项(item)在程序中有效的范围。
下面这个例子,重点关注变量:let s = "hello"

1
2
3
4
5
6
7
fn main() {
{ // s 在这里无效, 它尚未声明
let s = "hello"; // 从此处起,s 是有效的

// 使用 s
} // 此作用域已结束,s 不再有效
}

s 进入作用域时,它就是有效的。
这一直持续到它 离开作用域 为止。

s 离开作用域的时候。当变量离开作用域,Rust 为我们调用一个特殊的函数。这个函数叫做 drop,在这里 String 的作者可以放置释放内存的代码。
Rust 在结尾的 }自动调用 drop

Rust 采取了一个不同的策略:内存在拥有它的变量离开作用域后就被自动释放
核心就一句话,变量出了作用域,rust帮你释放了!!

3.移动

在 rust 当中一个变量指向另一个变量,并不是地址或引用的copy,而是称之为:移动
当 s2=s1 时,引用s1被移动到s2上,这和其它编程语言完全不同!!
下面这段代码,在其它编程语言上指针s1指向了指针s1s1仍然有效,在rust当中,s1无效已经无效。
在rust中,这个操作被称为 移动(move),而不是叫做浅拷贝。

1
2
3
4
5
6
fn main() {
let s1 = String::from("hello");
let s2 = s1;

println!("{}, world!", s1);
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:5:28
|
2 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 | let s2 = s1;
| -- value moved here
4 |
5 | println!("{}, world!", s1);
| ^^ value borrowed here after move
|

为什么要这么设计?
为了防止二次释放
s2s1 离开作用域,他们都会尝试释放相同的内存。
这是一个叫做 二次释放(double free)的错误,也是之前提到过的内存安全性 bug 之一。
两次释放(相同)内存会导致内存污染,它可能会导致潜在的安全漏洞。

如何保证,即要、也要

即要把s2=s1,也可保持s1可用,那就显示拷贝。

4.引用和借用 reference & borrowing

借用(borrowing)

借用就是字面意思,借来的数据,你并不拥有它。
看个例子:

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let s1 = String::from("hello");
// len 借用 s1
let len = calculate_length(&s1);

println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
s.len()
}

s1 移进了 calculate_length,但是所有权并没有转移,这里只是借用了s1
也就是说:指向 值 s1 的引用,但是并不拥有它。
因为并不拥有这个值,所以当引用停止使用时,它所指向的值也不会被丢弃。

借用默认不允许修改值

这个是反例,当去改变一个借用的数据时,就会报错。

1
2
3
4
5
6
7
8
9
fn main() {
let s = String::from("hello");

change(&s);
}

fn change(some_string: &String) {
some_string.push_str(", world");
}