ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

深入 Rust 多重借用(Multiple Borrows):多个参数借用与返回值生命周期解析

深入 Rust 多重借用(Multiple Borrows):多个参数借用与返回值生命周期解析 深入 Rust 多重借用Multiple Borrows多个参数借用与返回值生命周期解析【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust本文是 Google Android 团队开源 Rust 课程 comprehensive-rust 中 Lifetimes 章节 的核心教学单元解读。当函数接收多个引用参数、却只返回其中一个时借用检查器如何决定返回值延长哪个参数的借用读完本文你将掌握生命周期标注语法a的引入时机、仅看签名不看函数体的借用分析原则以及多重借用下a 7为何被禁止的根本原因。问题的起点一个无法编译的函数在课程章节 Multiple Borrows 中开篇给出了这样一段刻意写坏的代码该代码段在课程中标记为compile_fail即预期编译失败fn multiple(a: i32, b: i32) - i32 { todo!(Return either a or b) } fn main() { let mut a 5; let mut b 10; let r multiple(a, b); // Which one is still borrowed? // Should either mutation be allowed? a 7; b 7; dbg!(r); }这段代码有两个疑点也正是本课的核心问题multiple接收了两个引用a和b却要返回一个引用i32——返回值到底借用哪一个参数调用之后a 7和b 7这两处赋值哪一处应该被禁止直观上函数体内todo!()说明我们尚未决定返回a还是b。但这恰恰暴露了一个关键问题仅仅阅读函数签名编译器根本无从判断返回值与哪个参数存在借用关系。为什么编译失败缺少生命周期标注这个例子无法编译的直接原因正如课程笔记所指出的This code does not compile right now because it is missing lifetime annotations这段代码目前无法编译因为它缺少生命周期标注。在此之前课程已经在 Returning Borrows 中铺垫了一个更简单的场景fn identity(x: i32) - i32 { x } fn main() { let mut x 123; let out identity(x); // x 5; // ️❌ x is still borrowed! dbg!(out); }在identity中只传入一个借用因此返回的借用只能是那一个场景足够简单无需显式标注。而在multiple中出现了两个入参借用、一个出参借用借用关系出现了歧义——这就是本课要引入生命周期标注语法的契机。注意从 Rust 的生命周期省略规则见 Lifetime Elision来看当函数有多个入参引用且返回值也是引用时编译器无法自动确定返回值与哪个入参相关必须由程序员显式标注。核心原则借用分析只看函数签名本课最重要的教学要点是借用检查的一个容易被初学者忽略的事实When borrow checking, the compiler doesnt look at the body ofmultipleto reason about the borrows flowing out, instead it looks only at the signature of the function for borrow analysis. 进行借用检查时编译器不会查看multiple的函数体来推断流出的借用而只依据函数签名做借用分析。这是一个反直觉但极其重要的设计借用关系的契约由签名声明函数体只是对该契约的实现与校验。这意味着即使函数体内部逻辑明确只返回a或只返回b只要签名没有表达这一点调用点的借用检查就不会受益反过来签名一旦声明了借用关系即使函数体实现有误例如返回了错误生命周期的引用借用检查器也会在函数体内报错强制实现遵守签名契约。签名契约的强制校验这一点在下一课 Borrow One 中有更完整的验证。find_nearest的签名是fn find_nearesta(points: a [Point], query: Point) - a Point签名明确声明返回值只与points共享生命周期a。课程笔记指出若将函数体最后一行改为return query借用检查器会立刻报错——The borrow checker verifies that the logic in the function body actually returns a reference with the correct lifetime, enforcing that the function adheres to the contract set by the functions signature借用检查器会验证函数体逻辑确实返回了正确生命周期的引用强制函数遵守签名所设定的契约。解决方案引入生命周期标注语法回到本课的核心课程在讲解编译器错误之后引入了生命周期标注语法fn multiplea(a: a i32, b: a i32) - a i32 { ... }语法解读a声明了一个生命周期参数a表示某个尚未指定的存活区间a: a i32表示a这个引用的存活期是ab: a i32表示b的引用同样存活a- a i32表示返回值引用的存活期也是a。三个位置共用同一个a向编译器传达的信息是返回值的生命周期与a、b两者的借用生命周期绑定在一起。共用同一个a的后果同时借用两个参数当多个参数与返回值共享同一个生命周期时后果由下一课 Borrow Both 明确揭示we use the same lifetime for bothaandb, along with the return type. This means that the returned reference will borrowBOTHaandb! 我们对a、b和返回类型使用同一个生命周期这意味着返回的引用会同时借用a和b课程用pick函数演示了这一点fn picka(c: bool, a: a i32, b: a i32) - a i32 { if c { a } else { b } } fn main() { let mut a 5; let mut b 10; let r pick(true, a, b); // Which one is still borrowed? // Should either mutation be allowed? // a 7; // b 7; dbg!(r); }尽管运行时r实际只指向a和b中的某一个但编译期借用检查无法区分因此取消a 7或b 7中任意一行的注释都会导致编译失败——因为r在编译期被视为同时借用两者。这正是签名说了算、函数体不算数原则的必然结果。回到本课的multiple函数如果按课程给出的签名补全fn multiplea(a: a i32, b: a i32) - a i32那么调用后的a 7与b 7都将被禁止直到r的生命周期结束。两种策略对比同时借用 vs 精确借用综合 Borrow Both 与 Borrow One 两课当函数多进一出时程序员有两种表达策略策略签名形式语义对调用点的影响同时借用共享生命周期fn multiplea(a: a i32, b: a i32) - a i32返回值与两个入参都绑定a调用期间a、b都不能被可变借用/移动即使运行时只用到其中一个精确借用独立生命周期fn find_nearesta(points: a [Point], query: Point) - a Point返回值只与points绑定query不参与调用期间points被借用但query可被自由 drop 或修改Borrow One 中的find_nearest是精确借用的典型示范签名中query: Point没有生命周期标注在省略规则下获得独立的、与返回值无关的生命周期因此调用后可以立即drop(query)let nearest find_nearest(points, query); // query isnt borrowed at this point. drop(query); dbg!(nearest);课程笔记明确指出When we callfind_nearestthe returned reference doesnt borrowquery, and so we are free to drop it whilenearestis still active.更进一步的灵活性在于生命周期子类型subtyping如果确实希望允许返回query可以为query引入第二个生命周期b并加上约束b: ab至少与a活得一样久这样较长生命周期的引用可以安全地当作较短生命周期使用。在课程体系中的位置与实战联系章节脉络从 src/SUMMARY.md 的目录结构可以看到Lifetimes 章节的讲授顺序为Borrowing and Functions——借用仅在函数调用期间有效Returning Borrows——借用可以流出函数单参数场景无需标注Multiple Borrows——本课多参数场景暴露歧义引入生命周期语法Borrow Both——共享a的语义与代价Borrow One——独立生命周期的精确借用Lifetime Elision——三条省略规则的语法糖Lifetimes in Data Structures——结构体中的生命周期Exercise: Protobuf Parsing——综合实战。本课处于单参数很简单与多参数如何表达的转折点上是理解后续 Elision 规则和结构体生命周期的必要前提。实战价值Protobuf 解析练习中的生命周期本章的实战练习 Exercise: Protobuf Parsing 要求实现一个轻量 protobuf 解析器其中大量使用了本课所学的多借用分析。其参考实现 exercise.rs 展示了典型的签名设计// exercise.rs 中的关键签名见 src/lifetimes/exercise.rs fn parse_field(data: [u8]) - (Field_, [u8]) fn parse_messagea, T: ProtoMessagea(mut data: a [u8]) - Tparse_field接收一个字节切片借用返回解析出的Field其中FieldValue::Len(a [u8])是对输入数据的借用以及剩余的字节切片——两个出参都来自同一个入参data因此在省略规则下可以隐式共享生命周期。而parse_messagea, T: ProtoMessagea则将a贯穿于输入数据与T的实现中保证解析出的结构体字段始终借用原始输入而非复制数据。这正是本课多个借用流入、一个借用流出思想在生产代码中的应用解析类函数几乎总是借入一段数据借出一段指向该数据内部的结果生命周期的正确表达直接决定了 API 的安全性与可用性。小结本课三个必须带走的知识点多参数借用需要显式生命周期标注当函数有多个入参引用、且返回引用时省略规则无法自动确定借用关系必须用a语法声明。借用检查只看签名不看函数体签名是借用关系的契约函数体是实现调用点的借用分析完全基于签名展开。共享生命周期意味着同时借用若多个参数与返回值共用同一个a则调用期间这些参数都会被借用锁定哪怕运行时只实际使用其中一个想要更精确的借用关系应使用独立的生命周期参数可配合b: a子类型约束。建议的课堂演示路径课程笔记multiple-borrows.md 的details部分给出了 5 分钟讲解的推荐节奏作为自学同样适用先展示compile_fail版本观察编译器报错讨论multiple返回的引用应该延长a还是b的借用我们是否需要看到函数体才能判断答案不需要且看了也没用——借用分析只看签名思考后会发现信息不足这正是引入a语法的时机写出fn multiplea(a: a i32, b: a i32) - a i32进入下一课 Borrow Both 讨论同时借用的具体后果。最终当你能够流畅解释为什么multiple(a, b)之后两个都会被拒绝你就真正掌握了 Rust 借用检查中最核心的签名契约思维。【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表