Rust Ownership
Table of Contents
1. Rust Ownership
1.1. move
三种和 ownership 有关的变量传递方法:
- move
- copy
- borrow
move 和 copy 都属于传值, borrow 属于传引用. 其中 move 对应着 bitwise copy, copy 对应 deep copy
rust 不会自动实现 copy, 所以默认情况都是使用 move, 即 bitwise copy
#[derive(Debug)] // Test 默认为 move 语义 struct Test(i32, String); fn get_data() -> Test { let ret = Test(1, "hello".to_owned()); let px = &ret as *const _; let p1 = &ret.1 as *const _ as *const [u64; 3]; // test 结构体在栈上, i32 成员在0x7ffc2ed2a280,string 结构体在 // 0x7ffc2ed2a298, 所以 string 结构体的大小为 0x18 (24), 因为 string 有三个 // i64成员 (pointer_to_heap, size, capacity), 大小刚好是 24 println!("get_data"); println!("{:?}", (px, p1)); unsafe { println!("{:?}", *p1); } // string 值为 [140450239479816, 5, 5], 其中 140450239479816 是堆上的地址, 5, 5 是 size 和 capacity // 返回时发生了 move, 即 bitwise copy return ret; } fn main() { let x = get_data(); let px = &x as *const _; let p1 = &x.1 as *const _ as *const [u64; 3]; // (0x7ffc2ed2a3e0, 0x7ffc2ed2a3e0) // 由于 bitwise copy, px, p1 的址址都变化了 println!("after return"); println!("{:?}", (px, p1)); unsafe { // 但 p1 的值没有变, 因为 bitwise copy 时 copy 了 p1 的值 (string 三元组) println!("{:?}", *p1); } } // (0x7ffc2ed2a280, 0x7ffc2ed2a298, 0x7ffc2ed2a280) // [140450239479816, 5, 5] // (0x7ffc2ed2a3e0, 0x7ffc2ed2a3e0) // [140450239479816, 5, 5]
get_data (0x7ffd55a61ab0, 0x7ffd55a61ab0) [5, 110986105547648, 5] after return (0x7ffd55a61c50, 0x7ffd55a61c50) [5, 110986105547648, 5]
即使对于简单的赋值, move 也是 bitwise copy, 而不是简单的 alias
struct Test(i32); fn main() { let t = Test(10); println!("{:?}", &t as *const _); let pt = &t as *const _ as *const [i32; 1]; unsafe { println!("{:?}", *pt); } let t2 = t; println!("{:?}", &t2 as *const _); let pt2 = &t2 as *const _ as *const [i32; 1]; unsafe { println!("{:?}", *pt2); } }
0x7ffd4c8304fc [10] 0x7ffd4c8305ac [10]
1.2. clone
clone trait 实现了 deep copy, 对 string 来说, 它的 clone 实现会复现堆上的 string, 而不仅仅是 string 三元组.
#[derive(Debug, Clone)] struct Test(i32, String); fn get_data() -> Test { let ret = Test(1, "hello".to_owned()); let px = &ret as *const _; let p0 = &ret.0 as *const _; let p1 = &ret.1 as *const _ as *const [u64; 3]; println!("{:?}", (px, p0, p1)); unsafe { println!("{:?}", *p1); } return ret.clone(); } fn main() { let x = get_data(); let px = &x as *const _; let p1 = &x.1 as *const _ as *const [u64; 3]; println!("{:?}", (px, p1)); unsafe { println!("{:?}", *p1); } }
(0x7ffc263500f8, 0x7ffc26350110, 0x7ffc263500f8) [5, 107972209712000, 5] (0x7ffc26350260, 0x7ffc26350260) [5, 107972209712032, 5]
由于 string 实现了 clone trait, ret.clone() 返回的变量被 move 到调用者的 x 后, 看到的 string 在 heap 上的位置变化了.
1.3. copy
copy 相当于`自动 clone`, 即对应于实现了 copy trait 的变量 x, y=x 时相当于 y=x.clone()
实现 copy trait 会在不注意的情况下引用性能开销, 因为 string 只是实现了 clone, 并没有实现 copy.
需要注意的是, Copy trait 依赖于 Clone trait, 但 derive(Copy) 时使用的是 bit-wise clone, 即使有实现自定义的 clone
1.4. borrow
move, copy 即传值, borrow 即传引用.
shared reference 存在时原变量无法再被修改, 以避免 `concurrent modification`, 另外, reference 必须始终引用到有效的值, 被 borrow 的资源也无法被 move
无法 move:
#[derive(Debug)] struct Test(String); fn main() { let t = Test("hello".to_owned()); let pt = &t; let x = pt.0; println!("{:?}", x); } // --> quickrun:7:13 // | //7 | let x = pt.0; // | ^^^^ // | | // | cannot move out of borrowed content // | help: consider borrowing here: `&pt.0` // //error[E0507]: cannot move out of `pt.0` which is behind a `&` reference
无法修改:
#[derive(Debug)] struct Test(String); fn main() { let mut t = Test("hello".to_owned()); let pt = &t; t.0 = "world".to_owned(); println!("{:?}", pt); } // error[E0506]: cannot assign to `t.0` because it is borrowed // --> quickrun:7:5 // | // 6 | let pt = &t; // | -- borrow of `t.0` occurs here // 7 | t.0 = "world".to_owned(); // | ^^^ assignment to borrowed `t.0` occurs here // 8 | println!("{:?}", pt); // | -- borrow later used here // // error: aborting due to previous error // // For more information about this error, try `rustc --explain E0506`.
1.5. ownership
上面提到的 move, copy, borrow 与传统的变量传递方法没有什么区别. rust 的独特之处是在这之上的 ownership 机制:
- owner 是指变量所引用的资源的 owner
- owner 离开作用域时负责其资源的释放
- 为防止 double free, 每个资源只有一个 owner.
- bitwise copy 会共享相同的资源, 因此 bitwise copy 时 owner 会 move 到新的变量上
borrow 不会拥有资源, 也就不会释放资源, 但 rust 编译器会保证被 borrow 的资源是有效的, 例如:
- owner 的 life time 大于 borrow 的 life time, 防止 borrow 引用了已经释放的资源
- 被 borrow 的资源无法被 move
- 被 borrow 的资源无法被修改
fn main() { let y: &String; { let x = "hello".to_owned(); // y 并不是 x 的 owner, 但它的 life time 给 owner x 要长, 所以编译出错 y = &x; } println!("{:?}", y); } // error[E0597]: `x` does not live long enough // --> quickrun:6:13 // | // 6 | y = &x; // | ^^ borrowed value does not live long enough // 7 | } // | - `x` dropped here while still borrowed // 8 | // 9 | println!("{:?}", y); // | - borrow later used here // // error: aborting due to previous error // // For more information about this error, try `rustc --explain E0597`.
1.6. slice
slice 是 reference, 它们本身就是 borrow
fn main() { let t = "hello".to_owned(); let pt:&str = &t[1..2]; // t 无法被 mov, 因为它已经被 pt borrow let _t2 = t; println!("{:?}", pt); }
slice 实现上包含了额外信息: slice 的 ptr 指向原始数据中的起始位置, 另一个 len 成员表示 slice 的长度
fn main() { let t = "hello".to_owned(); let p1 = &t as *const _ as *const [u64; 3]; unsafe { println!("{:?}", *p1); } let pt = &t[1..]; println!("{:?}", pt); let p2 = &pt as *const _ as *const [u64; 2]; unsafe { println!("{:?}", *p2); } let pt = &t[2..]; println!("{:?}", pt); let p2 = &pt as *const _ as *const [u64; 2]; unsafe { println!("{:?}", *p2); } }
[5 (\, 106094169496448) (\, 5)]
1.7. mutable borrow
mutable borrow 的规则与读写锁类似:
- mutable borrow 只能有一个
- mutable borrow 不能与非 mutable borrow 共存
- 多个非 mutable borrow 可以共存
fn main() { let mut x = 10; let y1 = &mut x; println!("{:?}", y1); // 两个 &mut 不能同时有效 let y2 = &mut x; println!("{:?}", y2); *y1 = 1; *y2 = 2; }
1.8. 关于 borrow
extern void *get_buff(); int main(int argc, char *argv[]) { void *buff = get_buff(); return 0; }
谁负责 get_buff 释放 get_buff 返回的资源?
rust 的 borrow 规则决定了调用者不是资源的 owner, 所以对 rust 来说, 总是被调用者负责释放资源
fn get_buff() -> &str { let mut s = String::new(); s.push_str("hello"); return &s; }
以上代码无法编译, 因为 get_buff 返回后 s 即被释放. 需要给返回的引用找一个 lifetime 足够长的 owner, 例如:
fn get_buff2() -> &'static str { let s = "hello"; return s; } fn get_buff<'a>(s: &'a str) -> &'a str { return s; }
1.9. 总结
- move 即 bitwise copy
- copy 即 deep copy
- borrow 即 reference
- move 导致 owner 改变
- borrow 不改变 owner, 但需要确保被 borrow 的数据有效: life time 足够长, 不能被 move
- mutable borrow 的规则类似于读写锁