文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
4.1 所有权
所有权(ownership)目的是安全管理内存等资源,及时有效释放。
RAII :任何对象在离开作用域时,析构函数被调用,它所持有资源被释放。
资源属于谁有明确的规则,一旦所有者生命周期结束,则关联资源立即被释放。相比垃圾回收,单一所有者可实现编译期检查,插入自动释放指令。不但高效和安全,且更容易通过代码控制目标生命周期。
规则:
- 每个值(value)都有一个所有者(owner)。
- 同一时刻,值只能有一个所有者。
- 当所有者超出范围(scope),值被释放(drop)。
fn main() { { let _s = String::from("abc"); } // _s drop! println!("exit"); }
扩展:
- 所有权可转移(move),新所有者负责释放。
- 如果值实现
Copy
特征,以复制代替转移。 - 获取引用(reference),实现非所有权关联。
- 引用计数(
Rc
、Arc
),“多” 所有者。
转移
赋值、传参和返回值都可能导致所有权转移。转移后,原绑定失效,成未初始化状态(不能使用,除非重新绑定)。值由新所有者控制生命周期,并负责释放。
fn main() { let s = String::from("abc"); { let s2 = s; - value moved here println!("{:?}", s2); } println!("{:?}", s); ^ error: value borrowed here after move }
fn main() { let s = String::from("abc"); let t = false; if t { let _a = s; // move } else { let _b = s; // move } println!("{:?}", s); ^ value borrowed here after move }
fn main() { let s = String::from("abc"); let t = false; while t { let _a = s; ^ value moved here, in previous iteration of loop } }
fn main() { let v = vec![1, 2, 3]; for e in v { - `v` moved due to this implicit call to `.into_iter()` println!("{:?}", e); } println!("{:?}", v); ^ value borrowed here after move }
转移时,可更改可变性。
fn main() { let s = String::from("abc"); let mut s2 = s; s2.push_str("def"); println!("{:?}", s2); }
不能通过索引(array, vector ...)转移元素所有权。
如要移除元素,可调用容器
pop
、remove
方法,或std::mem::replace
替换。
fn main() { let mut v = vec!["a".to_string(), "b".to_string()]; // let s = v[0]; // ^~~~ error: cannot move out of index let s = &mut v[0]; s.push('!'); println!("{:?}", v); }
如果值实现了 Copy
特征,表示可复制,就不存在转移问题。标量类型( i32
, ...)实现了复制特征,且元素为复制类型的元组和数组也会复制。
注意,
Copy
是隐式调用,Clone
需要显式调用。
fn main() { let s = [1, 2, 3, 4]; { let s2 = s; // copy println!("{:?}", s2); } println!("{:?}", s); }
实现
利用反汇编观察所有权转移前后的释放操作。
fn main() { let _s = String::from("abc"); }
=> 0x000055555555a85b <+11>: mov rdi,rsp 0x000055555555a85e <+14>: mov rsi,rax 0x000055555555a861 <+17>: mov edx,0x3 0x000055555555a866 <+22>: call 0x55555555a110 <...::from> 0x000055555555a86b <+27>: mov rdi,rsp 0x000055555555a86e <+30>: call 0x55555555a2e0 <...::drop_in_place>
释放操作由编译器插入的 drop_in_place
完成。新所有者超出作用域后,立即释放。
fn main() { let s = String::from("abc"); { let _s2 = s; // 转移。 } // 此处释放! let _x = 100; // 识别外层作用域。 }
1 fn main() { 2 let s = String::from("abc"); 0x000055555555a85b <+11>: mov rdi,rsp 0x000055555555a85e <+14>: mov rsi,rax 0x000055555555a861 <+17>: mov edx,0x3 0x000055555555a866 <+22>: call 0x55555555a110 <...::from> 3 4 { 5 let _s2 = s; 0x000055555555a86b <+27>: mov rax,QWORD PTR [rsp] 0x000055555555a86f <+31>: mov QWORD PTR [rsp+0x18],rax ; _s2.ptr ... ; _s2.cap, len 6 } 0x000055555555a888 <+56>: lea rdi,[rsp+0x18] ; _s2 0x000055555555a88d <+61>: call 0x55555555a2e0 <...::drop_in_place> 7 8 let _x = 100; 9 }
传递
从语义看,复制操作克隆新实体,其后各不相干。转移则将目标对象 “移交” 给新所有者。
fn test(s: String) { let _s2 = s; } fn main() { let s = String::from("abc"); test(s); }
(gdb) b 2 (gdb) b 7 (gdb) r (gdb) disass/s ; 反汇编 main 函数。 5 fn main() { 6 let s = String::from("abc"); 0x000055555555922b <+11>: lea rdi,[rsp+0x8] 0x0000555555559230 <+16>: mov rsi,rax 0x0000555555559233 <+19>: mov edx,0x3 0x0000555555559238 <+24>: call 0x55555555aae0 <...::from> 7 test(s); => 0x000055555555923d <+29>: mov rax,QWORD PTR [rsp+0x8] 0x0000555555559242 <+34>: mov QWORD PTR [rsp+0x20],rax ; s.ptr ... ; s.cap, len 0x000055555555925b <+59>: lea rdi,[rsp+0x20] ; rdi --> test 0x0000555555559260 <+64>: call 0x5555555591f0 <demo::test> 8 } 0x0000555555559265 <+69>: add rsp,0x38 0x0000555555559269 <+73>: ret End of assembler dump. (gdb) b *0x0000555555559260 ; 调用 test 前。 (gdb) c (gdb) x/2xg $rsp+0x8 ; 函数 from 返回值。 0x7fffffffe338: 0x000055555559e9d0 0x0000000000000003 (gdb) x/s 0x000055555559e9d0 ; 字符串内容序列。 0x55555559e9d0: "abc" (gdb) x/2xg $rsp+0x20 ; 本地变量 s 内容。 0x7fffffffe350: 0x000055555559e9d0 0x0000000000000003 (gdb) c (gdb) disass/s ; 反汇编 test 函数。 1 fn test(s: String) { 2 let _s2 = s; => 0x00005555555591f4 <+4>: mov rax,QWORD PTR [rdi] ; s.ptr 0x00005555555591f7 <+7>: mov QWORD PTR [rsp],rax ; _s2 0x00005555555591fb <+11>: mov rax,QWORD PTR [rdi+0x8] ; s.cap 0x00005555555591ff <+15>: mov QWORD PTR [rsp+0x8],rax 0x0000555555559204 <+20>: mov rax,QWORD PTR [rdi+0x10] ; s.len 0x0000555555559208 <+24>: mov QWORD PTR [rsp+0x10],rax 3 } 0x000055555555920d <+29>: mov rdi,rsp ; _s2.drop! 0x0000555555559210 <+32>: call 0x55555555ac30 <...::drop_in_place>
可以看出,栈上的数据被复制。新拥有者负责释放堆内存,也就是说
drop
只会执行一次。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论