OBRM #
OBRM(Owner Base Resource Manage)
规则:
- 每一个 Rust 对象都有一个称为 Owner 的变量;
- 某个时刻,每个 Rust 对象只能有一个所有者(Onwer);
- 当改对象不再使用时,Rust 会调用它的
Drop trait
实现来释放资源;
通过 OBRM
,Rust 实现了自动内存(栈内存、堆内存)管理,而且在编译时即进行严格检查,而不需要像 C 那样手动释放堆内存,也实现了资源(文件、锁)的自动释放。
- Rust 程序 panic 时,通过
stack unwiding
机制,也会自动释放栈上创建的对象。
fn memory_example() {
let b = Box::new(23); // 堆上分配内存
let s = String::from("abxd"); // 堆上分配内存
panic!("just panic"); // panic 时通过 stack unwiding 机制,释放栈上创建的对象内存
println!("should be not pringed");
}
Ownership #
Rust 对象具有唯一所有权(Ownership
),可以通过赋值、函数传参、函数返回、添加到 struct/tuple
、类型解构等被转移(move)对象所有权,
原来的变量变成未初始化状态, 不能再使用, 这样可以避免悬挂指针(dangling pointers
)。这种所有权转移的方式,也避免了堆内存拷贝,在性能上和安全性上都是非常高效的。
Rust 变量的可见性和对象的 lifetime
是不同的概念:变量的可见性一般是 block scope
,而对象的 lifetime
则不是,例如函数内创建
的对象可以通过返回值转移到函数外部继续使用。
// 所有权转移:从栈上返回对象
fn new_person() -> Person {
let person = Person {
name : String::from("Hao Chen"),
age : 44,
sex : Sex::Male,
email: String::from("[email protected]"),
};
return person;
}
如果对象类型实现了 Copy trait
),则不会进行所有权转移,Rust 在必要时会会调用 栈内存的 bit-copy 来返回一个新对象。
- 堆上分配的对象,如
String/Vec/trait object
没有实现Copy trait
。 - 闭包、Struct、Enum、Union 等自定义类型,是否实现
Copy trait
,取决于内部对象或捕获的上下文对象是否都实现了Copy trait
。
类型还可以实现 Clone trait
, 这样可以生成一个新的对象,可以避免所有权发生转移。
通过使用 Box/Rc/Arc
等智能指针类型,可以将本来在栈上分配的对象在堆上进行分配:
fn main() {
let _box2 = Box::new(5i32);
// 嵌套 scope
{
let _box3 = Box::new(4i32); // Box: 在堆上分配 i32 内存
// `_box3` is destroyed here, and memory gets freed
}
for _ in 0u32..1_000 {
create_box();
}
// `_box2` is destroyed here, and memory gets freed
}
为了不获得对象所有权的情况下使用对象,Rust 通过借用操作(borrow/mut borrow
)来获得对象的引用,或者通过引用计数类型,如 Rc/Arc
来
clone
对象(不会拷贝对象的堆数据,但是只读的)。
Borrow Checker #
编译时,Rust borrow checker
对所有权和借用进行检查,违反时编译报错:
- 对象可以多次共享借用
&T
,但是只能一次可变借用&mut T
。 - 可变借用的对象本身必须是可变的,即只能对
mut
对象进行&mut
,或从已有&mut
生成新的&mut
&mut
借用不能重合,如不能同时对一个 struct 成员进行多次 &mut,但是不同成员 OK;
&mut T
可以自动隐式协变(type coerced
)到&T
,所以在需要 &T 的地方可以传入 &mut T 类型值,但是反过来不行;- 对象在存在借用的情况下不能被修改或转移;《== 借用冻结
- Vec、Array、Slice 的元素被借用时,整体上不能被修改和转移;
- 特殊情况:struct、enum、tuple 的成员可以被部分转移,而且不同 filed、元素可以单独被 &mut 。
- 不能通过借用 (&/&mut) 来转移对象或对象内部的子对象的所有权(例如,不同通过 &mut 来转移 Vec 中元素的所有权);
- 借用必须是有效的,必须不能有悬挂指针;
Rust borrow checker
将变量视为 所有权树的根 ,如果要修改对象的成员(如 struct 的 field)、集合中的元素等,需要使用
&mut self
,它将 &mut
引用从对象的根传递到最内层对象。这里的修改包括 3 方面:
- 对对象本身进行修改;
- 对对象的成员或元素进行修改;
- 调用对象或成员的方法,它们会改变对象的状态和内部字段等;
借用冻结和元素借用 #
对于 Vec/Array/Slice
类型对象,共享借用对象的某个元素时,
会导致对象整体被共享借用,进而导致对象整体不能被修改和移动,也不能被可变借用、转移其它元素;《== 某个元素被共享借用时,导致整体或其它元素冻结。
对于 Struct/Tuple/Enum 对象,共享借用对象的某个成员或元素时, 并不会导致对象整体被共享借用,可以可变借用其它成员或元素(或更深层次的成员或元素),但是一旦可变借用某个成员或元素,对象整体会被可变借用; 《=== 某个 成员或元素的共享借用,不影响其它成员或元素的可变借用;
注:上图中的上下级被染色的元素,对应的是 Struct/Tuple/Enum 类型对象的部分成员或元素。
// 对 Vec、Arrary、Slice 的成员共享借用会导致整体的共享借用,这里的成员可以是 struct 成员的 field 等;
let mut v = vec![1,2,3];
let v1 = &v[1];
v[2] = 2; // error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
println!("{v:?}, {v1}");
let mut a = [0;10];
let a1 = &a[1];
a[2] = 2; // error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
println!("{a:?}, {a1}");
let mut s1 = Student{name: "s1".to_string(), age:23};
let mut s2 = Student{name: "s2".to_string(), age:23};
let mut vs = vec![s1, s2];
//error[E0507]: cannot move out of index of `Vec<Student>`
// move occurs because value has type `String`, which does not implement the `Copy` trait
let vs0_name = vs[0].name;
let vs0_name = &vs[0].name; // 对 Vec、Arrary、Slice 成员的借用,会导致整体的共享借用
//error[E0502]: cannot borrow `vs` as mutable because it is also borrowed as immutable
let vs0_age = &mut vs[0].age; // 由于 vs 整体已经是共享借用,所以不能再可变借用
*vs0_age = 24;
println!("{vs0_age} {vs0_name}");
// 对 struct 成员的共享借用,不影响其它成员的可变借用, 但是一旦对其它成员进行可变借用,整体也被可变借用
#[derive(Debug)]
struct Student {
name: String,
age: i32,
}
let mut z = Student{name: "zzz".to_string(), age:23};
//let zr = &z.name;
//let zr2 = &z.age;
//z.age = 34; //error[E0506]: cannot assign to `z.age` because it is borrowed
//let zr3 = &mut z.name; //cannot borrow `z.name` as mutable because it is also borrowed
//println!("{z:?}, {zr:?}, {zr2:?}");
let zr = &z.name; // 并不会导致对 z 本身的共享或可变借用
z.age = 34; // OK
let za = &mut z.age; // 对不同成员进行可变可用,这会导致 z 本身进行了 &mut 借用
*za = 45;
// println!("{z:?}, {za}, {zr:?}"); // error[E0502]: cannot borrow `z` as immutable because it is also borrowed as mutable
println!("{z:?}, {zr:?}"); // OK, 因为后面没有使用 za,所以 za 对 z 本省的 &mut 借用失效
let name = z.name;
println!("{name}"); // OK
let mut t = (1, 2, 3);
let t1 = &t.1;
t.2 = 2;
let t2 = &mut t.2; // 对不同元素进行可变借用,这会导致对 t 本身进行了 &mut 借用
*t2 = 22;
//println!("{t:?} {t1} {t2}"); //error[E0502]: cannot borrow `t` as immutable because it is also borrowed as mutable
println!("{t1} {t2}");
示例:
let mut v = (136, 139);
let m = &mut v;
let m0 = &mut m.0;
*m0 = 137;
// ok: reborrowing mutable from mutable
let r1 = &m.1; // ok: reborrowing shared from mutable, and doesn't overlap with m0
// error: access through other paths still forbidden
v.1;
println!("{}", r1); // r1 gets used here
let mut a = 123;
let ar = &a;
// a = 456; // Error:cannot assign to `a` because it is borrowed,借用冻结
println!("{ar}")
let mut s = String::from_str("new string").unwrap();
// s 本身必须是 mut 类型才能被 &mut,在有 &mut 的情况下,原始值 s 不能再被访问。
let sm = &mut s;
// cannot borrow `s` as immutable because it is also borrowed as mutable
// println!("Result: {s} {sm}");
println!("Result: {sm}"); // OK
let s2 = &mut String::from_str("new string").unwrap();
// let sm2 = &mut s2; // s2 不是 mut 类型,不能 &mut;
s2.push_str(" abc"); // s2 是 &mut 借用类型,所以可以修改值
println!("s2: {s2}");
let s3 = s2; // s3 也是 &mut 类型, 可以修改
s3.push_str(" def");
println!("s3: {s3}");
// s3 虽然是 &mut 类型,但是不能通过借引用来 move
// let s4 = *s3;
// 在已经 &mut T 的情况下,原始值不能再被访问 《=== 借用冻结
let mut s = String::from_str("new string").unwrap();
// s 本身必须是 mut 类型,才能被 &mut;
let sm = &mut s;
s.push_str("abc"); // Error: 在 sm 后续继续使用的情况下,原来的 s 不能再使用。
// 不能同时使用 s 和 sm。
println!("Result: {s} {sm}");
struct MyStruct(u8, String);
let mut ms = MyStruct(3, "test".to_string());
let msm = &mut ms;
let msm2 = &mut msm.1; // 可以从 &mut 创建出另一个 &mut
msm2.push_str(" def");
// 字面量也支持借用,如 &13i32。
Rust 提供了内部可变性机制和类型,如 Cell/RefCell/Mutex/Rwlock
,可以使用共享借用 &self
来修改对象值:
// data 不可变。
let data = Arc::new(Mutex::new(0));
// MutextGuard 支持内部可变性,故可以获得 mut data。
let mut data = data.lock().unwrap();
*data += 1;
在转移对象的所有权时可以改变它的可变性(因为转移到的变量完全拥有该对象,所以可以重新定义可见性):
let immutable_box = Box::new(5u32);
println!("immutable_box contains {}", immutable_box);
// Mutability error
//*immutable_box = 4;
// 转移对象时,改变它的可变性
let mut mutable_box = immutable_box;
println!("mutable_box contains {}", mutable_box);
*mutable_box = 4; // 修改可变对象值
println!("mutable_box now contains {}", mutable_box);
不能通过借用(无论是可变还是共享借用)来对对象或子对象所有权进行转移,例如 let v2 = *V
是错误的。
解决办法:
- V 如果实现
Copy/Clone trait
,则可以调用对应的方法来创建新对象; - 使用
std::mem::replace(&mut dest, src)
将 dest 用 src 替换,同时返回 dest 的值: - 使用
std::ptr::read(src: *const T) -> T
来浅拷贝对象(新对象指向老对象的内存区域),然后使用std::ptr::write(&mut dest, src)
来不 drop dest 的情况下,将 src 写入 dest。
// Error 的例子
struct Buffer {
buffer : String,
}
struct Render {
current_buffer : Buffer,
next_buffer : Buffer,
}
impl Render {
fn update_buffer(&mut self, buf : String) {
// error[E0507]: cannot move out of `self.next_buffer` which is behind a mutable reference
// move occurs because `self.next_buffer` has type `Buffer`, which does not implement the
// `Copy` trait
self.current_buffer = self.next_buffer;
self.next_buffer = Buffer{ buffer: buf};
}
}
// OK:使用 std::mem::replace() 替换
fn update_buffer(&mut self, buf: String) {
self.current_buffer = std::mem::replace(&mut self.next_buffer, Buffer{buffer : buf});
}
// OK:使用 std::ptr::read/write 来临时转移借用对象的内容
//
// ptr::read(src: *const T) -> T
//
// 从 src 指针处浅复制(类似于 Copy)获取 T 对象,它共享 src 指向的内存区域数据。
unsafe {
let result = ::std::ptr::read(dest); // result 共享 dest 指向的内存区域数据
std::ptr::write(dest, src); // 向 dest 写入 src 对象(src 对象所有权被转移), 但不 drop dest
result
}
// Ok 的例子:struct、tuple 支持部分转移
#[derive(Debug)]
struct Person {
name: String,
email: String,
}
fn main() {
let mut p = Person{name: "zzz".to_string(), email: "fff".to_string()};
let _name = p.name; // struct 允许部分 move
println!("{} {}", _name, p.email); // 可以访问没有 move 的 struct 成员
// println!("{:?}", p); //出错:不允许访问部分 move 的 struct 整体
p.name = "zhang jun".to_string(); // OK
println!("{:?}", p); // OK
}
NLL Reborrow #
如果 let rr = &mut r
的 rr 不再使用,则可以再次对 r 进行借用,该特性称为 NLL
:Non-Lexical
Lifetime
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
impl Point {
fn move_to(&mut self, x: i32, y: i32) {
self.x = x;
self.y = y;
}
}
fn main() {
let mut p = Point { x: 0, y: 0 };
let r = &mut p;
// 错误:Point 未实现 Copy,不能通过解引用 *r 来转移值
// let p2 = *r
let rr: &Point = &*r; // 重新借用
println!("{:?}", rr); // Reborrow ends here, NLL introduced
// 由于共享借用 rr 不再使用,所以 r 可以重新被借用
r.move_to(10, 10);
println!("{:?}", r);
}
解引用操作符 * #
对于自定义类型,需要实现 Deref/DerefMut trait
后才支持解引用操作符 *。
T 的 Deref/DerefMut trait
返回的是 &Target/&mut Target
,而 *T
的结果是
Target
,即自动解引用,这样支持左值赋值,如 *T = v;
。
- 作为右值时返回引用的对象值;
- 作为左值时可以用于修改对象的值;
常见技巧: 如果 T 实现了 Deref<Target=U>
,则 &*T
返回 &U
类型值,常用于不支持自动通过 Deref
来匹配函数参数限界的场景。
由于编译器支持常见场景的自动解引用和生成引用,所以实际很少需要显式解引用。
. 操作符 #
按需自动借用或解引用:
- 通过 . 操作符访问对象成员时,自动解引用,返回成员的值,如果该成员类型不支持 Copy 则会转移所有权(可能会失败):
- 如:
ref.filed
等效于(*ref).field
。(不能是*ref.field
,因为 . 的优先级比 * 高。)
- 通过 . 操作符调用方法时,如果方法的第一个参数是
&self
或&mut self
, 则自动借用对象来生成引用,然后传递给对应的方法;
- 如:
v.sort()
等效为(&v).sort()
,称为method call deref coercion
;
// 使用 . 访问 引用变量的成员时,自动解引用,返回成员值
struct Anime { name: &'static str, bechdel_pass: bool };
let aria = Anime { name: "Aria: The Animation", bechdel_pass: true };
let anime_ref = &aria;
assert_eq!(anime_ref.name, "Aria: The Animation");
// 等效于
assert_eq!((*anime_ref).name, "Aria: The Animation");
// 对象方法是 &self 或 &mut self 时,自动借用变量,生成它的引用
let mut v = vec![1973, 1968];
v.sort(); // 自动借用 v,生成 &v 来调用 sort() 方法
// 等效于
(&mut v).sort();
let x = 5;
let y = &x;
assert_eq!(5, *y); // 直接使用 ref 变量时需要显式解引用
如前面讨论,对于 Vec、Arrary、Slice 的元素或更深层次的对象的借用,都会导致这些对象整体被共享借用。
let mut s1 = Student{name: "s1".to_string(), age:23};
let mut s2 = Student{name: "s2".to_string(), age:23};
let mut vs = vec![s1, s2];
//error[E0507]: cannot move out of index of `Vec<Student>`
// move occurs because value has type `String`, which does not implement the `Copy` trait
//let vs0_name = vs[0].name;
let vs0_name = &vs[0].name; // 对 Vec、Arrary、Slice 成员的借用,会导致整体的共享借用
//error[E0502]: cannot borrow `vs` as mutable because it is also borrowed as immutable
let vs0_age = &mut vs[0].age; // 由于 vs 整体已经是共享借用,所以不能再可变借用
*vs0_age = 24;
println!("{vs0_age} {vs0_name}");
其它自动解引用场景(都支持多级自动解引用):
- 比较操作(
Ord/PartialOrd/Eq/PartialEq
):因为比较的是 引用的值而非引用本身(是指针); - index 操作符,如
a[i]
返回的是对象本身; - 算术运算操作(Rust 不支持对引用的指针进行算术运算);
println!()/assert_*
等宏函数:它们的参数都是自动被借用,然后内部自动解引用传入的对象后进行显示(而不是显示引用自身);
struct Point { x: i32, y: i32 }
let point = Point { x: 1000, y: 729 };
let r: &Point = &point;
let rr: &&Point = &r;
let rrr: &&&Point = &rr;
assert_eq!(rrr.y, 729); // . 操作符:自动解多级引用
let x = 10;
let y = 10;
let rx = &x;
let ry = &y;
let rrx = ℞
let rry = &ry;
assert!(rrx <= rry); // 比较操作:自动解多级引用
assert!(rrx == rry);
fn factorial(n: usize) -> usize {
(1..n+1).product()
}
let r = &factorial(6);
assert_eq!(r + &1009, 1729); // 算术运算:自动解引用
let v1: Vec<i32> = vec![1, 2, 3];
v1.iter().map(|x| x + 1); // x 是 &i32 类型,算术运算自动解引用
Rust 可以对任意表达式结果进行借用,包括字面量,Rust 自动使用 type coercion
对引用类型进行隐式转换:
// 字面量借用
// 算术操作:自动解引用
r + &1009;
// 隐式类型转换
let _: &i8 = &mut 42;
fn bar(_: &i8) { }
fn main() {
bar(&mut 42);
let x = 5;
let y = &x;
assert_eq!(5, *y);
}
对于 T,&T,&mut T,Box<T>
进行 Display、比较、assert 时,操作的对象都是借用的对象本身。
如果要比较引用地址,需要使用 std::ptr::eq()
函数。
&T, &mut T, Box<T>
等借用类型也是指针,可以使用{:p}
来显示地址而非借用的对象;
let mut t = 123;
let tp = &mut t;
let tpp = &tp; // 从 &mut T 可以再借用出共享引用 &T
println!("{:p} {:p}", tp, tpp); // tp 和 tpp 是两个不同类型的变量,所以地址不同
// Rc::clone() 只是增加了引用计数,所以产生的对象与以前的对象是相同的地址;
let rc = Rc::new(String::from("abc"));
let rc2 = rc.clone();
// rc: abc, rc2: abc, rc pointer:0x600001bdc2b0, rc2 pointer 0x600001bdc2b0
// 可见 rc 和 rc2 内存的地址都是一样的,说明 Rc clone 没有发生堆内存拷贝。
println!("rc: {}, rc2: {}, rc pointer:{:p}, rc2 pointer {:p}", rc, rc2, rc, rc2);
let five = 5;
let other_five = 5;
let five_ref = &five;
let same_five_ref = &five;
let other_five_ref = &other_five;
assert!(five_ref == same_five_ref); // 比较操作时,自动多级解引用,所以比较的是值。
assert!(five_ref == other_five_ref);
assert!(std::ptr::eq(five_ref, same_five_ref)); // std::ptr::eq 比较内存地址
assert!(!std::ptr::eq(five_ref, other_five_ref));