陈天 · Rust 编程第一课
陈天
Tubi TV 研发副总裁
23195 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 65 讲
基础篇 (21讲)
陈天 · Rust 编程第一课
15
15
1.0x
00:00/00:00
登录|注册

08|所有权:值的借用是如何工作的?

活跃的可变引用和只读引用是互斥的
仅允许一个活跃的可变引用
借用的约束:借用不能超过值的生存期
借用者不能修改被借用的值
默认情况下,Rust 的借用都是只读的
引用实现了 Copy trait
Rust 中的参数传递都是传值
引用是一个受控的指针
可变引用和只读引用共存的问题
多个可变引用共存的问题
可变引用的约束
借用的生命周期及其约束
只读引用的特性
引用的特性
引用语法(& 或者 &mut)
引用的生命周期不能超出值的生命周期
一个值可以有唯一一个活跃的可变引用
一个值可以有多个只读引用
如果值实现了 Copy trait,赋值或传参会使用 Copy 语义
一个值在同一时刻只有一个所有者
可变借用/引用
只读借用/引用
Rust 编译器通过检查来确保代码没有违背规则
修改代码使其编译通过,避免同时有只读引用和可变引用
上一讲讲 Copy trait 时为什么可变引用没有实现 Copy trait
Borrow 语义
小结
思考题
值的借用
Rust Borrow 语义

该思维导图由 AI 生成,仅供参考

你好,我是陈天。
上一讲我们学习了 Rust 所有权的基本规则,在 Rust 下,值有单一的所有者。
当我们进行变量赋值、传参和函数返回时,如果涉及的数据结构没有实现 Copy trait,就会默认使用 Move 语义转移值的所有权,失去所有权的变量将无法继续访问原来的数据;如果数据结构实现了 Copy trait,就会使用 Copy 语义,自动把值复制一份,原有的变量还能继续访问。
虽然,单一所有权解决了其它语言中值被任意共享带来的问题,但也引发了一些不便。我们上一讲提到:当你不希望值的所有权被转移,又因为没有实现 Copy trait 而无法使用 Copy 语义,怎么办?你可以“借用”数据,也就是这一讲我们要继续介绍的 Borrow 语义。

Borrow 语义

顾名思义,Borrow 语义允许一个值的所有权,在不发生转移的情况下,被其它上下文使用。就好像住酒店或者租房那样,旅客 / 租客只有房间的临时使用权,但没有它的所有权。另外,Borrow 语义通过引用语法(& 或者 &mut)来实现。
看到这里,你是不是有点迷惑了,怎么引入了一个“借用”的新概念,但是又写“引用”语法呢?
其实,在 Rust 中,“借用”和“引用”是一个概念,只不过在其他语言中引用的意义和 Rust 不同,所以 Rust 提出了新概念“借用”,便于区分。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

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-11
    8
  • 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-08
    3
    22
  • 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-08
    6
    19
  • 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-08
    3
    13
  • bekyiu
    let 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-08
    16
  • 彭亚伦
    第一题 可变引用如果实现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-25
    2
    9
  • gnu
    fn 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-08
    8
  • 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-26
    7
  • HiNeNi
    copy语义和move语义底层实现都是按位浅拷贝,只不过copy语义是产生新的值,move语义是所有权转移,这样理解对吧?

    作者回复: 正确

    2021-09-14
    7
  • 罗杰
    有且仅有一个活跃的可变引用存在,对于这句话还是要好好理解一下。

    作者回复: 嗯,「活跃的」这个定语是 Rust 编译器做的一个优化,可以让我们不用添加不必要的作用域。可以简单这么认为:在撰写代码的时候,如果你在某处使用了一个可变引用之后就再也没用了,那么这处之后的地方这个可变引用就不是活跃的了。

    2021-09-10
    2
    5
收起评论
显示
设置
留言
57
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部