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又做了一个借用的概念, 也就是说:

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在两个不同线程