You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust LinkedList中pop方法的借用检查器错误原因排查

Rust LinkedList pop方法的可变引用冲突问题

问题重现

在实现LinkedList的尾部弹出元素pop方法时,遇到了奇怪的编译错误:当添加if next.next.is_none()判断来定位倒数第二个节点时,触发了双重可变引用的E0499错误;但把这个判断注释掉后,代码反而能正常编译。

错误代码

fn pop(&mut self) -> Option<T> {
    match self.head {
        None => None,
        Some(ref mut head) => {
            let mut curr = head;

            while let Some(ref mut next) = curr.next {
                if next.next.is_none() {
                    break;
                }

                curr = next;
            }

            let taken = curr.next.take();
            taken.map(|x| x.data)
        }
    }
}

可编译代码

fn pop(&mut self) -> Option<T> {
    match self.head {
        None => None,
        Some(ref mut head) => {
            let mut curr = head;

            while let Some(ref mut next) = curr.next {
                // if next.next.is_none() {
                //     break;
                // }

                curr = next;
            }

            let taken = curr.next.take();
            taken.map(|x| x.data)
        }
    }
}

错误信息

error[E0499]: cannot borrow `curr.next` as mutable more than once at a time
  --> src\main.rs:70:29
   |
62 |                 while let Some(ref mut next) = curr.next {
   |                                ------------ first mutable borrow occurs here
...
70 |                 let taken = curr.next.take();
   |                             ^^^^^^^^^^^^^^^^
   |                             |
   |                             second mutable borrow occurs here
   |                             first borrow later used here

原因分析

这是Rust借用检查器的严格规则导致的:

  • 在错误代码的while let Some(ref mut next) = curr.next中,我们对curr.next创建了一个可变借用并绑定到next上。
  • 当添加if next.next.is_none()判断时,编译器会认为这个可变借用next的生命周期需要覆盖到判断语句结束——直到break跳出循环,curr.next的可变借用都没有释放。
  • 后续执行curr.next.take()时,又尝试对curr.next进行第二次可变借用,违反了Rust"同一时间只能有一个可变引用"的核心规则,因此报错。

而注释掉判断后,循环会一直执行到curr.next为None,此时循环内的可变借用已经完全释放,后续的curr.next.take()就能正常获取新的可变引用,所以编译通过。

修复方案

核心思路是:避免在持有curr.next的可变引用时进行判断,改用不可变引用来检查是否到达倒数第二个节点,让借用检查器能正确识别借用的生命周期。

修改后的pop方法如下:

fn pop(&mut self) -> Option<T> {
    match self.head {
        None => None,
        Some(ref mut head) => {
            let mut curr = head;

            // 循环条件改为:当前节点的下一个节点还有后续节点时,继续移动
            while curr.next.as_ref().map_or(false, |n| n.next.is_some()) {
                // 临时获取可变引用并移动curr,借用会立即释放
                curr = curr.next.as_mut().unwrap();
            }

            // 此时curr是倒数第二个节点(或头节点如果链表只有一个元素)
            let taken = curr.next.take();
            // 别忘了更新链表长度
            if taken.is_some() {
                self.len -= 1;
            }
            taken.map(|x| x.data)
        }
    }
}

为什么这样能解决问题?

  • curr.next.as_ref()获取的是不可变引用,用来检查next.next是否存在,不会占用curr.next的可变借用权限。
  • 循环内的curr.next.as_mut().unwrap()是临时的可变借用,完成curr的移动后就立即释放,不会影响后续的curr.next.take()操作。
  • 同时补充了self.len的更新,修正了原代码中长度字段不一致的问题。

完整修复后的代码

use std::fmt::Debug;

#[derive(Debug)]
struct Node<T> {
    data: T,
    next: Option<Box<Node<T>>>,
}

#[derive(Debug)]
struct LinkedList<T> {
    head: Option<Box<Node<T>>>,
    len: usize,
}

impl<T: Debug> LinkedList<T> {
    fn new() -> Self {
        Self { head: None, len: 0 }
    }

    fn push(&mut self, data: T) {
        match self.head {
            None => self.head = Some(Box::new(Node::new(data))),
            Some(ref mut head) => {
                let mut curr = head;

                while let Some(ref mut next) = curr.next {
                    curr = next;
                }

                curr.next = Some(Box::new(Node::new(data)));
            }
        }

        self.len += 1;
    }

    fn pop(&mut self) -> Option<T> {
        match self.head {
            None => None,
            Some(ref mut head) => {
                let mut curr = head;

                while curr.next.as_ref().map_or(false, |n| n.next.is_some()) {
                    curr = curr.next.as_mut().unwrap();
                }

                let taken = curr.next.take();
                if taken.is_some() {
                    self.len -= 1;
                }
                taken.map(|x| x.data)
            }
        }
    }
}

impl<T> Node<T> {
    fn new(data: T) -> Self {
        Node { data, next: None }
    }
}

内容的提问来源于stack exchange,提问作者Cyan Froste

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 18:06:02