如何确保函数调用后不可变借用的结束以启用可变借用?

7
我在使用Rust 2018时遇到了一个borrowchecker问题,无法找到解决方法。基本上,我有一个函数,它接受一个可变引用类型的vec,并将该相同的vec作为其执行的第一部分传递给另一个函数作为不可变引用类型。后一个函数返回一个新的拥有值 - 或者至少是我打算这样做的。对我来说,问题在于编译器似乎认为函数调用的不可变借用会持续到外部函数结束。
不幸的是,这不是简单地将括号放在周围就可以解决的问题(无论如何,因为我正在使用Rust 2018)。此外,虽然我发现了一些看起来涉及类似问题的SO问题(例如这个这个这个这个),但我没有能够找到其他直接解决此问题的内容。关键是,大多数其他类似问题似乎要求返回类型为引用,或者仅在非词法生存期之前存在问题。
我在Rust Playground中创建了一个可执行的MVE,并在需要时添加了完整程序。我将代码放在下面供参考:
// This function was blatantly borrowed from a Stack Overflow post
// but unfortunately I lost track of which one.
fn compute_mean_of_vec<'g, T>(input_vec: &'g [T]) -> T
where
    T: Copy
        + num::Zero
        + std::ops::Add<T, Output = T>
        + std::ops::Div<T, Output = T>
        + num::FromPrimitive
        + std::iter::Sum<&'g T>,
{
    let sum: T = input_vec.iter().sum();
    sum / num::FromPrimitive::from_usize(input_vec.len()).unwrap()
}

fn normalise_cost_vec<'a, T>(cost_vec: &'a mut Vec<T>)
where
    T: std::ops::SubAssign
        + Copy
        + num::traits::identities::Zero
        + std::ops::Div<Output = T>
        + num::traits::cast::FromPrimitive
        + std::iter::Sum<&'a T>,
{
    let mean = compute_mean_of_vec(cost_vec);
    for c in cost_vec.iter_mut() {
        *c -= mean;
    }
}

fn main() {
    let mut my_vec = vec![5.0f32; 5];
    normalise_cost_vec(&mut my_vec);
    for e in my_vec.iter() {
        println!("{}", e);
    }
}

编译器产生的错误信息是:
error[E0502]: cannot borrow `*cost_vec` as mutable because it is also borrowed as immutable
  --> src/main.rs:26:14
   |
16 | fn normalise_cost_vec<'a, T>(cost_vec: &'a mut Vec<T>)
   |                       -- lifetime `'a` defined here
...
25 |     let mean = compute_mean_of_vec(cost_vec);
   |                -----------------------------
   |                |                   |
   |                |                   immutable borrow occurs here
   |                argument requires that `*cost_vec` is borrowed for `'a`
26 |     for c in cost_vec.iter_mut() {
   |              ^^^^^^^^ mutable borrow occurs here

看起来,根据错误消息,我觉得这两个函数指定的生命周期可能存在问题。我得承认,我加进去的生命周期基本上只是根据编译器和 Clippy 的建议放进去的,我不是完全理解它们。我能说的最好的是,编译器似乎认为在调用 compute_mean_of_vec 时的不可变借用应该持续到整个调用 normalise_cost_vec 的过程中。
我做错了什么呢?我要怎样让编译器满意呢?我猜这可能涉及指定另一个生命周期,但是尽管看了 The Book 和一些在线资源,我还是没有找到正确的方法。
3个回答

3

看起来问题出在Sum trait的生命周期参数上,以下是一种不需要去除此trait的解决方案

fn compute_mean_of_vec<'g, T>(input_vec: &'g Vec<T>) -> T
where
    for<'x> T: Copy
        + num::Zero
        + std::ops::Add<T, Output = T>
        + std::ops::Div<T, Output = T>
        + num::FromPrimitive
        + std::iter::Sum<&'x T>,
{
    let sum: T = input_vec.iter().sum();
    sum / num::FromPrimitive::from_usize(input_vec.len()).unwrap()
}

fn normalise_cost_vec<'a, T>(cost_vec: &'a mut Vec<T>)
where
    for<'x> T: std::ops::SubAssign
        + Copy
        + num::traits::identities::Zero
        + std::ops::Div<Output = T>
        + num::traits::cast::FromPrimitive
        + std::iter::Sum<&'x T>,
{
    let mean = compute_mean_of_vec(cost_vec);
    for c in cost_vec.iter_mut() {
        *c -= mean;
    }
}

fn main() {
    let mut my_vec = vec![5.0f32; 5];
    normalise_cost_vec(&mut my_vec);
    for e in my_vec.iter() {
        println!("{}", e);
    }
}

即,通过为trait Sum 指定一个独立的生命周期参数'g,参数'g不会被认为是在整个函数中传递。

这正是我一直在思考和希望能够实现的。非常感谢,这完美地解决了我的问题! :) - Jarak

3
问题出在 Sum 特质上,让我们看一下它的声明:
pub trait Sum<A = Self> {
    fn sum<I>(iter: I) -> Self
    where
        I: Iterator<Item = A>;
}
这意味着,函数有一个绑定到它的引用,在函数结束后仍然有效(理论上)。因此,您会得到一个“也作为不可变借用”的错误。
解决方法是,不使用Sum trait,而是使用fold,因为您已经有了一个默认值(num::Zero),以及T所需的Add trait。
fn compute_mean_of_vec<'g, T>(input_vec: &'g [T]) -> T
where
    T: Copy
        + num::Zero
        + std::ops::Add<T, Output = T>
        + std::ops::Div<T, Output = T>
        + num::FromPrimitive,
{
    let sum: T = input_vec.iter().fold(T::zero(), |a, e| a + *e);
    sum / num::FromPrimitive::from_usize(input_vec.len()).unwrap()
}

fn normalise_cost_vec<'a, T>(cost_vec: &'a mut Vec<T>)
where
    T: std::ops::SubAssign
        + Copy
        + num::traits::identities::Zero
        + std::ops::Div<Output = T>
        + num::traits::cast::FromPrimitive,
{
    let mean = compute_mean_of_vec(cost_vec);
    for c in cost_vec.iter_mut() {
        *c -= mean;
    }
}

(游乐场)

1
我选择了另一个答案作为“解决方案”,因为它让我继续使用sum,这是我更喜欢的方式,但我非常感谢您对问题和建议的清晰解释。谢谢@hellow! :) - Jarak

1
我找到的解决方案是不使用 std::iter::Sum,并使用 fold 重写 sum 调用:
fn compute_mean_of_vec<T>(input_vec: &[T]) -> T
where
    T: Copy
        + num::Zero
        + std::ops::Add<T, Output = T>
        + std::ops::Div<T, Output = T>
        + num::FromPrimitive,
{
    let sum: T = input_vec.into_iter().fold(T::zero(), |acc, &item| acc + item);
    sum / num::FromPrimitive::from_usize(input_vec.len()).unwrap()
}

因此,您不会将平均值绑定到输入 vec 的生命周期,编译器也会很高兴。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接