不能一次多次使用`x`作为可变的

tow*_*120 15 rust

在以下代码(playground)中:

struct Node {
    datum: &'static str,
    edges: Vec<Node>,
}

fn add<'a>(node: &'a mut Node, data: &'static str) -> &'a Node {
    node.edges.push(Node {
        datum: data,
        edges: Vec::new(),
    });
    &node.edges[node.edges.len() - 1] // return just added one
}

fn traverse<F>(root: &Node, callback: &F)
where
    F: Fn(&'static str),
{
    callback(root.datum);
    for node in &root.edges {
        traverse(node, callback);
    }
}

fn main() {
    let mut tree = Node {
        datum: "start",
        edges: Vec::new(),
    };

    let lvl1 = add(&mut tree, "level1");

    traverse(&mut tree, &|x| println!("{:}", x)); //I actually don't need mutability here
}
Run Code Online (Sandbox Code Playgroud)

我有这个错误:

error[E0499]: cannot borrow `tree` as mutable more than once at a time
  --> src/main.rs:32:19
   |
30 |     let lvl1 = add(&mut tree, "level1");
   |                         ---- first mutable borrow occurs here
31 | 
32 |     traverse(&mut tree, &|x| println!("{:}", x)); //I actually don't need mutability here
   |                   ^^^^ second mutable borrow occurs here
33 | }
   | - first borrow ends here
Run Code Online (Sandbox Code Playgroud)

我的问题似乎非常类似于 为什么Rust想要一次多次使用变量作为可变变量?,但我不确定.如果是这样,这种情况有解决方法吗?

Vla*_*eev 17

add是因为如何定义:

fn add<'a>(node: &'a mut Node, data: &'static str) -> &'a Node
Run Code Online (Sandbox Code Playgroud)

这里指定了结果引用的生命周期应该等于传入引用的生命周期.可能的唯一方法(除了不安全的代码)是结果引用以某种方式从传入引用派生,例如,它引用传入引用指向的对象内的一些字段:

struct X {
    a: u32,
    b: u32,
}

fn borrow_a<'a>(x: &'a mut X) -> &'a mut u32 {
    &mut x.a
}
Run Code Online (Sandbox Code Playgroud)

然而,有没有办法让编译器知道到底是什么从进入结构由只在函数签名(其中,在一般情况下,是在编译使用这种功能的代码时,它可以做的唯一的事情)看借来的.因此,它无法知道以下代码在技术上是正确的:

let mut x = X { a: 1, b: 2 };
let a = borrow_a(&mut x);
let b = &mut x.b;
Run Code Online (Sandbox Code Playgroud)

我们知道a并且b是不相交的,因为它们指向结构的不同部分,但是编译器无法知道,因为borrow_a签名中没有任何东西可以暗示它(并且不存在,Rust不支持它) .

因此,编译器可以做的唯一明智的事情是考虑整个 x被借用,直到返回的引用borrow_a()被删除.否则,可以为相同的数据创建两个可变引用,这违反了Rust别名保证.

请注意以下代码是正确的:

let mut x = X { a: 1, b: 2 };
let a = &mut x.a;
let b = &mut x.b;
Run Code Online (Sandbox Code Playgroud)

在这里,编译器可以看到a并且b从不指向相同的数据,即使它们确实指向同一结构内部.

没有解决方法,唯一的解决方案是重构代码,使其没有这种借用模式.

  • 编译器可以在将函数主体放在板条箱中时对其进行处理,但是您如何建议处理外部* binary *依赖性?它们没有足够的信息供编译器使用。因此,这是完善所有权和借用模型的唯一途径,是的,在很多情况下“他们做到了”,包括与数据竞赛作斗争。至于在这种情况下的编码方式,没有别的方法可以重组代码,例如,在特定情况下,您可以将插入和查找分为两个方法,其中插入不会返回任何内容,从而避免了借用。 (2认同)
  • 在“外部”下,我的意思是外部板条箱中的任意Rust代码,例如*不是*外部C代码(自然,所有外部代码都应通过不包含借入信息的不安全指针和原始指针进行处理)。您不建议使所有外部依赖项都不安全,是吗?例如,std中的许多代码都包含类似代码中的功能。真正意义上的重组不是对编译器有意义的代码重组,它只是遵循所有权和借用模型。锈静态分析*非常严格*,但这是有充分理由的。 (2认同)
  • @hdante,没有解决方法,在我的评论中说明了这一点,尽管答案本身并没有说明。我可能应该解决这个问题。 (2认同)

Vee*_*rac 5

该行为是合乎逻辑的。考虑什么

fn add<'a>(node: &'a mut Node, data: &'static str) -> &'a Node
Run Code Online (Sandbox Code Playgroud)

方法。

这表示它&mut Node的生命周期等于其返回值的生命周期。因为您将返回值分配给一个名称,所以它会一直存在到作用域末尾。因此,可变借用的寿命也很长。

如果您可以轻松丢弃返回值,请这样做。你可以把它扔在地板上:

let mut tree = Node {
    datum: "start",
    edges: Vec::new(),
};

add(&mut tree, "level1");

traverse(&mut tree, &|x| println!("{:}", x));
Run Code Online (Sandbox Code Playgroud)

或者您可以使用词法范围来约束它而不完全删除它。

如果你想借用回报而不强迫可变借用也活那么长时间,你可能必须将函数分成两部分。这是因为您无法借用可变借用的返回值来执行此操作。