08|所有权:值的借用是如何工作的?
该思维导图由 AI 生成,仅供参考
Borrow 语义
- 深入了解
- 翻译
- 解释
- 总结
Rust的所有权和借用机制是其独特的特点之一,通过Move语义、Copy语义和Borrow语义的灵活运用,有效地解决了其他语言中存在的问题,为程序员提供了更加安全和可靠的编程环境。Rust引入了Borrow语义,允许值的所有权在不发生转移的情况下被其他上下文使用,通过引用语法(&或者&mut)来实现。在Rust中,引用是一种借用了“临时使用权”,并不破坏值的单一所有权约束。Rust的引用实现了Copy trait,因此按照Copy语义,引用会被复制一份交给要调用的函数,而数据本身的所有权仍然在原有的拥有者那里。此外,Rust对值的引用也有生命周期约束,借用不能超过值的生存期。总之,Rust的所有权和借用机制在管理值的所有权和避免内存安全问题方面具有独特的优势。
《陈天 · Rust 编程第一课》,新⼈⾸单¥68
全部留言(57)
- 最新
- 精选
- Marvichov置顶1. 这篇也是常看常新: https://manishearth.github.io/blog/2015/05/17/the-problem-with-shared-mutability/; 这也能解释为什么就算单线程, 某个code entry只能有一个mutable reference 2. 第二题引出了non lexical lifetime; 感觉还是第一性原理 shared mutability: 某个code entry运行时, 有且只有一个mutable reference; 不违反, 就能work; 3. 租房那个例子太棒了! 房子到期了, 租户不能白嫖;
作者回复: 非常赞!
2021-09-118 - woshidag置顶第一题 可变引用可copy trait的话,相当于多个地方可以修改同一块数据,违背“一个值在同一时刻只有一个所有者” 第二题,用数组下标取值,i32实现了copy trait fn main() { let mut arr = vec![1, 2, 3]; // cache the last item let last = arr[arr.len()-1]; // let last = arr.last(); arr.push(4); // consume previously stored last item println!("last: {:?}", last); }
作者回复: 非常棒! 第二题也可以先使用,后 push。 ```rust fn main() { let mut arr = vec![1, 2, 3]; // cache the last item let last = arr.last(); // consume previously stored last item println!("last: {:?}", last); arr.push(4); } ```
2021-09-08322 - pedro置顶1. 上一讲我们在讲 Copy trait 时说到,可变引用没有实现 Copy trait。结合这一讲的内容,想想为什么? 在一个作用域内,仅允许一个活跃的可变引用,如果可以被 Copy,那还怎么玩。 下面这段代码,如何修改才能使其编译通过,避免同时有只读引用和可变引用? 究其根本原因在于,可变与不可变借用相互交缠,破坏了:活跃的可变引用(写)和只读引用(读)是互斥的,不能同时存在的原则,因此修改也很简单,把 arr.push 上移,或者下移,如下: fn main() { let mut arr = vec![1,2,3]; let last = arr.last(); println!("last: {:?}", last); arr.push(4); } 当然也可以上移到 last 前面。
作者回复: 非常正确!
2021-09-08619 - Ryan置顶堆变量的生命周期不具备任意长短的灵活性,因为堆上内存的生死存亡,跟栈上的所有者牢牢绑定。 这应该算是一个很强的限制,如果我希望有一段内存的生命周期是由我的业务逻辑决定的,在rust中要如何实现呢?这种情况下又如何让rust帮助我管理生命周期,减少错误呢?
作者回复: 你可以用 Box::leak / Box:into_raw / ManuallyDrop 让堆内存完全脱离自动管理。按照你的需求,你可以使用 ManuallyDrop。代码如下: ```rust use std::mem::ManuallyDrop; fn main() { // 使用 ManuallyDrop 封装数据结构使其不进行自动 drop let mut s = ManuallyDrop::new(String::from("Hello World!")); // ManuallyDrop 使用了 Deref trait 指向 T,所以可以当 String 使用 s.truncate(5); println!("s: {:?}", s); // 如果没有这句,s 不会在 scope 结束时被自动 drop(你可以注掉试一下) // 如果我们想让它可以自动 drop,可以用 into_inner let _: String = ManuallyDrop::into_inner(s); } ``` 更详细的代码可以看 playground(我实现了个 MyString,在 Drop trait 中加了打印,这样可以更清楚地看到 drop 是否被调用): https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=4362bb2f45d252b01822d9206b988019 至于 Box::leak / Box::into_raw,我们后续会慢慢讲到。
2021-09-08313 - bekyiulet mut data = vec![1, 2, 3, 4]; let b = &mut data; println!("sum of data1: {}", sum(b)); // ok println!("{:?}", b); 可变引用没有实现copy trait,为啥这样不会转移所有权呢
作者回复: 好问题。这涉及到 reborrow。在函数调用时,sum(b) 实际上等价于 sum(&mut *b)。reborrow 以后我们看有没有机会和 NLL (non-lexical lifetime) 加餐一起介绍一下。目前我们先理解好基本的所有权/借用/生命周期规则。reborrow / NLL 是为了让代码更简单易写而做的改进。 你可以通过下面的代码看 reborrow 和普通借用的区别: ```rust fn main() { let mut x = 42; let r1 = &mut x; // reborrow 可以通过 let r2 = &*r1; // &x 不可以 // let r2 = &x; println!("r1: {:p}, r2: {:p}", &r1, &r2); *r1 += 1; } ```
2021-09-0816 - 彭亚伦第一题 可变引用如果实现Copy Trait的话, 容易造成同一作用域内出现多个可变引用, 本质上会对同一数据有多个修改权限, 形成数据竞争, 会导致未定义行为,难以在运行时追踪,并且难以诊断和修复相当于同一时刻同一数据有多个所有者, 数据安全完全不可控, 因此可变引用不能实现Copy Trait. 第二题, 解决方案有不少 第一种, 把`arr.push(4)` 移动到打印下方 ```rsut fn main() { let mut arr = vec![1, 2, 3]; let last = arr.last(); println!("last: {:?}", last); arr.push(4); } ``` 这样做一开始是比较难以理解的, 因为可变引用和不可引用似乎都在main函数这同一个作用域内, 但是看过死灵书还是啥的大致就明白了, 其实是编译器自己做了优化, 添加了一些生命周期标志, 使得不可变引用的生命周期在打印调用完之后就提前结束了, 之后再使用可变引用就没问题了. 新版Book里面有: 编译器在作用域结束之前判断不再使用的引用的能力被称为非词法作用域生命周期(Non-Lexical Lifetimes,简称NLL). 还有一个是调用`Option`的方法cloned, 也算行的通吧, 哈哈: ```rsut fn main() { let mut arr = vec![1, 2, 3]; let last = arr.last().cloned(); arr.push(4); println!("last: {:?}", last); } ```
作者回复: 👍
2021-10-2529 - gnufn main() { let mut arr = vec![1, 2, 3]; // cache the last item let last = *arr.last().unwrap(); arr.push(4); // consume previously stored last item println!("last: {:?}", last); } 通过 解引用 让 last 成为分配在栈上的值,而不是指向堆上 arr 数组元素的指针,可以编译通过
作者回复: 嗯,这样也可以。这里隐含着使用了 i32 的 Copy trait,让 last 拷贝了一份。它对 Vec<String> 就不适用,因为 String 会做 move。你可以试着在 playground 运行这端代码看看出什么错误: ```rust fn main() { let mut arr = vec![String::from("a"), String::from("b")]; // cache the last item let last = *arr.last().unwrap(); arr.push(String::from("c")); // consume previously stored last item println!("last: {:?}", last); } ``` 还有其它解法,可以参考我之前的回答。
2021-09-088 - thanq第一题: 可变引用(eg: let mut v = vec![1] ; let mut r = v ), 如果实现了 Copy trait, 就会导致变量 r 创建时, 在栈上再复制一个胖指针, 该胖指针也会指向相同的堆内存, 且这两个胖指针所有权独立, 都可以发起该份堆内存数据的修改操作, 这样就无法保证内存安全. 所以, 出于内存安全的考虑, Rust对可变引用没有实现 Copy trait 第二题: 实例代码编译报错的原因是在变量 last 为对 arr 的只读借用还生效的情况下, 又尝试进行 arr 的可变借用, 而这两个操作是互斥的 解决方式有两个: 1 提前归还变量 last 对 arr 的只读借用 ``` fn main() { let mut arr = vec![1, 2, 3]; let last = arr.last(); println!("last: {:?}", last); // last 作用域结束, 归还arr的只读借用 arr.push(4); } ``` 2 变量 last 赋值不进行借用操作 ``` fn main() { let mut arr = vec![1, 2, 3]; //将整数值赋值给变量last, 此处不发生借用(&)操作 let last = arr[arr.len() - 1]; println!("last: {:?}", last); arr.push(4); println!("len: {:?}", len); } ```
作者回复: 非常棒!
2021-09-267 - HiNeNicopy语义和move语义底层实现都是按位浅拷贝,只不过copy语义是产生新的值,move语义是所有权转移,这样理解对吧?
作者回复: 正确
2021-09-147 - 罗杰有且仅有一个活跃的可变引用存在,对于这句话还是要好好理解一下。
作者回复: 嗯,「活跃的」这个定语是 Rust 编译器做的一个优化,可以让我们不用添加不必要的作用域。可以简单这么认为:在撰写代码的时候,如果你在某处使用了一个可变引用之后就再也没用了,那么这处之后的地方这个可变引用就不是活跃的了。
2021-09-1025