Rust学习笔记
初步学习, 感觉Rust的内存安全是靠编译的时候检查你的”错误写法”来实现的. 按网上的说法: c++开发, 经验教你做人; Rust开发, 编译器教你做人; 那么Rust在设计的时候, 就使用一些’原则’来避免你写出并发错误的程序. Rust就像那些C++书一样, 在不停的告诉你, 你不能这样写不能那样写;
1. 原则
就比如:多线程编程最容易写错的就是并发写, 而这个问题最最必要的一个条件就是:”当 2 个或更多个指针同时访问同一内存位置,当它们中至少有 1 个在写”; 两个线程访问一块内存, 肯定有两个分别位于各自线程的指针, 指向这个内存; 那试想一下,我规定一个内存只能有一个可写指针引用, 你可能写出错误的程序吗? 所以, Rust整个设计就是围绕这个部分展开的;
2. 所有权 和 move 语意
为了满足这个基本原则, Rust对所有资源(也可以理解为内存)做了’所有权’的概念. 同时设计了第一个原则:
- 一个资源只能有一个’变量’拥有所有权;
那当你把一个变量引用的资源像java哪样’赋值给’另一个变量的时候, 得到的结果不再是:两个变量同时引用(指向)这个资源
, 而是把资源的所有权交给它, 自己不再拥有(指向)资源
let v1 = vec![1, 2, 3];
println!("v1: {:p} = {:?}", &v1, v1);
{
let v2 = v1; // v1 的所有权 move 给了v2
println!("v2: {:p} = {:?}", &v2, v2); // v2 和 v1 是不同的地址
// println!("v1: {:p} = {:?}", &v1, v1); // 这里 v1 就不能再使用了
}
// println!("v1: {:p} = {:?}", &v1, v1); // 即使v2 离开作用域, 也不行. 交了就是交了, 不会还回来
好了, 现在v2获得所有权之后是不会再还给v1的; 满足了原则, 但是不能’=’操作了, 程序没法写了. 于是rust又做了一个借用
的概念, 也就是说:
- 在v2的作用域内, v1借给v2资源
- v2离开作用域, 还给v1
3. 借用
好, 既然可以借用, 那在v1v2共同的作用域内, 岂不是又有两个’变量’‘指向’同一个资源? 于是, Rust又设计了这样一个原则:
在同一个作用域内, 一个资源只可以有:
- 0 个或 N 个资源的不可变引用(&T)
- 只有 1 个可变引用((&mut T)
这不就是读写锁嘛. 对, rust就是在’变量赋值引用’做了hack, 进行内存的读写隔离!
看下面的代码:
let mut v1 = vec![1, 2, 3];
println!("v1: {:p} = {:?}", &v1, v1);
{
// <----------------作用域开始
let v2 = &v1; // 对资源的不可变引用
let v3 = &v1; // 不可变引用 2
println!("v2: {:p} = {:?}", v2, *v2);
println!("v3: {:p} = {:?}", v3, *v3);
// let v4 = &mut v1; // 已经有了不可变引用v2v3
// v1[1] = 99; // 作用域内, 有不可变引用, 就不能有可变引用; v1[1] 也是可变引用
} // <---------------作用域结束
let v5 = &mut v1; // <--- 在这里, 对资源只有一个可变引用, v2v3不在这个作用域
v5[1] = 99;
println!("v5: {:p} = {:?}", &v5, v5);
看下输出的结果:
v1: 0x700000403990 = [1, 2, 3]
v2(0x7000004038b8): 0x700000403990 = [1, 2, 3]
v3(0x7000004038b0): 0x700000403990 = [1, 2, 3]
v5(0x700000403708): 0x700000403990 = [1, 99, 3]
那么其实借用跟c的指针是一样的. 我们建了一个数组, 地址在’0x700000403990’, 然后建了一个指针v2指向这个数组, 指针本身的地址是’0x7000004038b8’ , 值是’0x700000403990’
说白了, rust的这个规则其实并不是’新的’, 我们写多线程程序也是要注意这些问题的; 只不过rust用’编译器检查’的方式帮你review代码;
作用域
再看借用的语法:
- 在你的作用域内, 我借给你
- 你离开作用域, 还给我
仔细考虑这个语法, 实际上包含了一个隐含原则:
- 借用者的作用域 比 被借用者的 作用域要 短
c 程序 一个无效指针的rust写法就是:
{
let mut v1 = vec![1, 2, 3];
println!("v1: {:p} = {:?}", &v1, v1);
let v2 = &v1; // v2 引用v1, 然后进入线程
let th = thread::spawn(move ||{
println!("v2: {:p} = {:?}", &v2, v2); //线程还在作用域
});
th.join();
} // <-- v1 退出作用域了, 但是v2却不一定, 因为v1v2在两个不同线程