为什么匹配的可变引用的条件赋值会导致借用错误?

kmd*_*eko 11 rust borrow-checker

我无法理解为什么这会导致错误:

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

pub fn print_root_or_next(root: &mut Node, try_next: bool) {
    let mut current = root;
    match &mut current.next {
        Some(node) => {
            if try_next {
                current = &mut *node;
            }
        }
        None => return,
    }

    println!("{:?}", current);
}
Run Code Online (Sandbox Code Playgroud)
#[derive(Debug)]
pub struct Node {
    next: Option<Box<Node>>,
}

pub fn print_root_or_next(root: &mut Node, try_next: bool) {
    let mut current = root;
    match &mut current.next {
        Some(node) => {
            if try_next {
                current = &mut *node;
            }
        }
        None => return,
    }

    println!("{:?}", current);
}
Run Code Online (Sandbox Code Playgroud)

我看不出有冲突的借用;甚至错误消息似乎表明两个借用是一个并且相同。这是借用检查器的问题还是这个例子实际上在某些方面存在缺陷?

我对这个限制很感兴趣。我对实现了解不多,但考虑到一个基本的if

if try_next {
    current = &mut *current.next.as_mut().unwrap();
}
Run Code Online (Sandbox Code Playgroud)

和基本的match

match &mut current.next {
    Some(node) => {
        current = &mut *node;
    }
    None => return,
}
Run Code Online (Sandbox Code Playgroud)

甚至反转它们:

if try_next {
    match &mut current.next {
        Some(node) => {
            current = &mut *node;
        }
        None => return,
    }
}
Run Code Online (Sandbox Code Playgroud)

所有的工作,一定有一些事情,借阅检查员正在考虑或没有考虑与我对原始表格为什么不起作用的理解相冲突。

rod*_*igo 5

我认为问题在于它current本身是有条件的再借用。

考虑这个删除条件但使用两个变量的更简单的代码:

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

pub fn print_root_or_next(root: &mut Node) {
    let a = root;
    let b;
    match &mut a.next {
        Some(node) => {
            b = &mut *node;
        }
        None => return,
    }

    println!("{:?}", a);
    println!("{:?}", b);
}
Run Code Online (Sandbox Code Playgroud)

这失败了,正如您对错误消息所期望的那样:

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

pub fn print_root_or_next(root: &mut Node) {
    let a = root;
    let b;
    match &mut a.next {
        Some(node) => {
            b = &mut *node;
        }
        None => return,
    }

    println!("{:?}", a);
    println!("{:?}", b);
}
Run Code Online (Sandbox Code Playgroud)

也就是说,a是可变借用的,并且该借用由于 保持活动状态b。所以你不能ab活着的时候使用。

如果你重新排序最后两println!行,它编译没有问题,因为词法生命周期:b被打印然后被遗忘,释放借用并a再次可用。

现在,看看另一个变体,与您的相似,但没有if

pub fn print_root_or_next(root: &mut Node) {
    let mut c = root;
    match &mut c.next {
        Some(node) => {
            c = &mut *node;
        }
        None => return,
    }
    println!("{:?}", c);
}
Run Code Online (Sandbox Code Playgroud)

它也可以很好地编译,因为在c重新借用时会重新分配。从这一点开始,它的工作方式与b上一个示例相同。你可以b自由地使用它,被禁止的是使用a,而这里不再可用。

回到你的代码:

pub fn print_root_or_next(root: &mut Node, test: bool) {
    let mut c = root;
    match &mut c.next {
        Some(node) => {
            if test {
                c = &mut *node;
            }
        }
        None => return,
    }
    println!("{:?}", c);
}
Run Code Online (Sandbox Code Playgroud)

这里的问题是,当c重新借用时,它是有条件地完成的,编译器不知道它将是哪一个,就像上面的例子a一样b。所以它必须假设它是同时的!但是从我们看到的例子中,你不能使用awhile bis alive ,但由于它们是相同的值,所以不能再使用这个值。

当编译器抱怨这个奇怪的错误消息时:

17 | , current);
   | ^^^^^^^
   | |
   | immutable borrow occurs here
   | mutable borrow later used here
Run Code Online (Sandbox Code Playgroud)

它实际上意味着:

17 | , current);
   | ^^^^^^^
   | |
   | immutable borrow occurs here if try_next is false
   | mutable borrow at the same time used here if try_next is true
Run Code Online (Sandbox Code Playgroud)

我不知道这是否是编译器的限制,使用这个有条件地重新借用本身的引用实际上是安全的。也许这是借用检查的限制,或者可能有一些我不明白的微妙之处......我怀疑如果您包含多个条件或多个参考,允许这样做可能是不合理的。

  • 根据记录,原始代码可以使用“-Zpolonius”编译良好,因此我怀疑允许这样做是合理的。它类似于最初的“[case #3](https://rust-lang.github.io/rfcs/2094-nll.html#problem-case-3-conditional-control-flow-across-functions)”应该由 NLL 修复,但不是出于编译器性能原因。 (3认同)