Rust中Option引用的模式匹配:为何部分写法无法通过借用检查
Rust 链表尾追加的编译行为差异
前置定义
我们首先基于Box实现一个最朴素的递归链表节点结构:
struct Node { next: Option<Box<Node>>, }
目标是实现一个方法:接收链表头节点的可变引用,遍历找到当前链表的最后一个节点,在其next字段上追加一个新的空节点。
编译失败的实现写法
以下三种写法逻辑完全一致,都会触发E0506编译错误:报错原因是编译器判定循环过程中对node.next的可变借用持续整个函数生命周期,因此循环结束后仍然无法对node.next执行赋值操作。
- 使用
as_deref_mut()拆值的版本
fn add1<'a>(mut node: &'a mut Node) { while let Some(next) = node.next.as_deref_mut() { node = next; } node.next = Some(Box::new(Node { next: None })); }
- 直接对
node.next取可变引用的版本
fn add1_borken_ergonomics(mut node: &mut Node) { while let Some(next) = &mut node.next { node = next; } node.next = Some(Box::new(Node { next: None })); }
- 手动对
node.next取双层可变引用的版本
fn add1_borken_explicit<'a>(mut node: &'a mut Node) { while let Some(ref mut next) = *&mut node.next { node = next; } node.next = Some(Box::new(Node { next: None })); }
可正常编译的等价实现
和上面的写法逻辑完全等价,仅循环内的模式匹配取值方式有细微区别,即可通过编译:
fn add1_working<'a>(mut node: &'a mut Node) { while let Some(ref mut next) = node.next { node = next; } node.next = Some(Box::new(Node { next: None })); }
核心差异来源
这类编译行为不一致不是Rust语言本身的规则差异,本质是Rust 2018版本引入的非词区生命周期(NLL)借用检查器的实现精度问题:
- 当直接在模式匹配中对
node.next使用ref mut按字段拆出可变引用时,NLL能准确跟踪借用的有效范围:每次循环中,对当前node所指节点的next字段的借用,会在node变量被重新赋值为新的next引用后立即失效,不存在跨迭代的悬挂借用,因此循环结束后可以正常对当前node的next字段赋值。 - 当使用
as_deref_mut()、直接写&mut node.next、对字段地址二次解引用这类写法时,借用检查器会采取保守判断:把这类写法产生的可变借用,判定为是对整个node可变变量本身的借用,而非仅仅对node指向对象内部next字段的借用。这种保守判断会错误地把循环中产生的借用生命周期锚定到函数入参的'a生命周期上,因此哪怕循环已经执行完毕,编译器仍然判定node.next处于被借用状态,阻断了最后的赋值操作。 - 这类行为已经被Rust编译器团队记录为待优化的实现缺陷:所有上述写法的语义完全等价,不存在代码合法性的区别,差异仅来自编译器对不同语法形式的借用跟踪能力不一致,不存在开发者需要额外记忆的特殊规则。
内容的提问来源于stack exchange,提问作者Fuyang Liu
相关产品推荐
相关产品推荐

