所有运行的程序都必须管理其使用计算机内存的方式。一些语言中具有垃圾回收机制,在程序运行时不断地寻找不再使用的内存;在另一些语言中,程序员必须亲自分配和释放内存。Rust 则选择了第三种方式:通过Ownership系统管理内存,编译器在编译时会根据一系列的规则进行检查。在运行时,所有权系统的任何功能都不会减慢程序。
Ownership
请谨记这些规则:
- Rust 中的每一个值都有一个被称为其 所有者(owner)的变量。
- 值在任一时刻有且只有一个所有者。
- 当(owner)所有者(变量)离开作用域,这个值将被丢弃。当变量离开作用域后,Rust 自动调用
drop
函数并清理变量的堆内存。
栈和堆
栈和堆都是代码在运行时可供使用的内存,但是它们的结构不同。栈以放入值的顺序存储值并以相反顺序取出值。这也被称作 后进先出。想象一下一叠盘子:当增加更多盘子时,把它们放在盘子堆的顶部,当需要盘子时,也从顶部拿走。不能从中间也不能从底部增加或拿走盘子!增加数据叫做 入栈,而移出数据叫做 出栈。
栈中的所有数据都必须占用已知且固定的大小。在编译时大小未知或大小可能变化的数据,要改为存储在堆上。堆是缺乏组织的:当向堆放入数据时,你要请求一定大小的空间。内存分配器(memory allocator)在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的 指针(pointer)。这个过程称作 在堆上分配内存(allocating on the heap),有时简称为 “分配”(allocating)。将数据推入栈中并不被认为是分配。因为指针的大小是已知并且固定的,你可以将指针存储在栈上,不过当需要实际数据时,必须访问指针。
想象一下去宾馆开房。当服务员给你房卡时,你能去楼上2203房间,你朋友来迟了要找你,他们也可以通过服务员你的房号来找到你在哪里。
字符串String
扫盲贴的类型都是已知大小的,可以存储在栈中,并且当离开作用域时被移出栈,如果代码的另一部分需要在不同的作用域中使用相同的值,可以快速简单地复制它们来创建一个新的独立实例。使用 String 作为例子可以更好地理解Rust如何在堆上存储数据
fn main() {
let s = String::from("hello");
}
String 由三部分组成,如图左侧所示:一个指向存放字符串内容内存的指针,一个长度,和一个容量。这一组数据存储在栈上。右侧则是堆上存放内容的内存部分,这里顺道画下栈上变量的复制。
两个冒号(::
)是运算符,用于调用 String 类型的命名空间(namespace)下的from 函数。
对字符串进行修改
fn main() {
let mut s = String::from("hello");
s.push_str(", world!"); // push_str() 在字符串后追加字面值
println!("{}", s); // 将打印 `hello, world!`
println!("test_copy_stack is {}", test_copy_stack(5));
println!(
"test_copy_stack is {}",
test_copy_heap("222".parse().unwrap())
);
}
fn test_copy_stack(z: i32) -> i32 {
println!("let Z is {}", z);
let x = 6;
let y = x;
let d = z + 1;
println!("let X is {}", x);
println!("let y is {}", y);
d
}
fn test_move_heap(z: String) -> String {
println!("let Z is {}", z);
let y = z;
// 如果你在其他语言中听说过术语 浅拷贝(shallow copy)和 深拷贝(deep copy),那么拷贝指针、长度和容量而不拷贝数据可能听起来像浅拷贝。不过因为 Rust 同时使第一个变量无效了,这个操作被称为 移动(move),而不是浅拷贝。
// println!("let Z is {}", z);
println!("let y is {}", y);
y
}
字符串move let y = z;
如果真的需要深度复制 String
中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做 clone
的通用函数,在作用域拥有2份堆上数据 除了作用域 俩个变量都会释放堆上的内存
fn test_clone_heap(z: String) -> String {
println!("let Z is {}", z);
let y = z.clone();
println!("let Z is {}", z);
println!("let y is {}", y);
y
}
Clone
ownership && Func
将值传递给函数在语义上与给变量赋值相似。向函数传递值可能会移动或者复制,就像赋值语句一样。返回值用于接收函数的返回所有权。
fn main() {
let s = String::from("hello"); // s 进入作用域
takes_ownership(s); // s 的值移动到函数里 ...
//println!("x is {}", s); 这里会报错 因为s以及呗移动了
// ^ value borrowed here after move
let x = 5; // x 进入作用域
makes_copy(x);
println!("x is {}", x);
}
fn takes_ownership(some_string: String) {
println!("{}", some_string);
}
fn makes_copy(some_integer: i32) {
println!("{}", some_integer);
}