所有权
所有权(系统)是 Rust 最为与众不同的特性。 它让 Rust 无需垃圾回收(garbage collector)即可保障内存安全。 所有权以及相关功能:借用(borrowing)、slice 以及 Rust 如何在内存中布局数据。
所有程序都必须管理其运行时使用计算机内存的方式。 一些语言中具有垃圾回收机制,如: java、python; 在另一些语言中,程序员必须亲自分配和释放内存,如:C/C ;
Rust 则选择了第三种方式:通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。
内存与分配
Rust 的所有权围绕着内存分配进行,Rust 对内存管理通过其所有权展开。
栈
它是一种 后进先出 的机制,类似我们日常的落盘子,只能一个一个向上方,然后从最上面拿一个盘子。
一个变量要放到栈上,那么它的大小在编译时就要明确。i32
类型的变量,它就占用 4
个字节。Rust 中可以放到栈上的数据类型,他们的大小都是固定的。
如果是字符串,在运行时才会赋值的变量,在编译期的时候大小是未知或不确定的。所以字符串类型存储在堆上。
堆
用于编译时大小未知或不确定的,只有运行时才能确定的数据。在堆上存储一些动态类型的数据。堆是不受系统管理的,是用户自己管理的,也增加了内存溢出的风险。
1.所有权规则
记住这三句话,整个所有权就是围绕这三句话,这三句话也直接概括了所有权。
- Rust 中的每一个值都有一个所有者(owner)。
- 值在任一时刻有且只有一个所有者。
- 当所有者(变量)离开作用域,这个值将被丢弃。
2.变量作用域
大部份编程语言都有 作用域(scope) 的概念,但是在rust中,这个概念被提到一个很重要的高度。
先看看rust一些变量的 作用域(scope)。
作用域是一个项(item)在程序中有效的范围。
下面这个例子,重点关注变量:let s = "hello"
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
指向了指针s1
,s1
仍然有效,在rust当中,s1
无效已经无效。
在rust中,这个操作被称为 移动(move),而不是叫做浅拷贝。
fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);
}
运行结果:
代码语言:javascript复制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
|
为什么要这么设计?
为了防止二次释放。
当 s2
和 s1
离开作用域,他们都会尝试释放相同的内存。
这是一个叫做 二次释放(double free)的错误,也是之前提到过的内存安全性 bug 之一。
两次释放(相同)内存会导致内存污染,它可能会导致潜在的安全漏洞。
如何保证,即要、也要
即要把s2=s1
,也可保持s1
可用,那就显示拷贝。
4.引用和借用 reference & borrowing
借用(borrowing)
借用就是字面意思,借来的数据,你并不拥有它。 看个例子:
代码语言:javascript复制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
的引用,但是并不拥有它。
因为并不拥有这个值,所以当引用停止使用时,它所指向的值也不会被丢弃。
借用默认不允许修改值
这个是反例,当去改变一个借用的数据时,就会报错。
代码语言:javascript复制fn main() {
let s = String::from("hello");
change(&s);
}
fn change(some_string: &String) {
some_string.push_str(", world");
}