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

时间:2019-08-05 05:00:51

标签: rust

我使用Rust 2018遇到了一个借贷检查器问题,但我找不到解决方案。基本上,我有一个对vec进行可变引用的函数,并且在执行的第一部分将该同一个vec传递给另一个函数作为不可变引用。后一个函数返回一个新的拥有值-或至少我打算这样做。对我来说,问题是编译器似乎认为函数调用的不可变借用一直持续到外部函数结束为止。

不幸的是,这不是一个简单的问题,只需在事情周围放上括号即可(因为我正在使用Rust 2018,所以无论如何都不应该这样)。此外,尽管我发现了许多类似问题的SO问题(例如thisthisthisthis),但我没有能够找到其他任何直接解决此问题的方法。或者至少,在我无法从中得出应该做的事情的地方,什么也没有。至关重要的是,大多数其他类似的问题似乎要么以引用作为返回类型,要么仅是非词汇生存期之前的问题。

我在Rust Playgroundfull program中创建了一个可执行的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的其余调用中。

我做错了什么,如何使编译器满意?我想这与指定另一个生存期有关,但是尽管查看了《书》和许多在线资源,但我仍无法找到正确的方法。

3 个答案:

答案 0 :(得分:3)

问题是Sum特质,让我们看一下它的declaration

pub trait Sum<A = Self> {
    fn sum<I>(iter: I) -> Self
    where
        I: Iterator<Item = A>;
}

这意味着,有一个绑定到该函数的引用,即使在函数结束(理论上)后,该引用也有效。因此,您会收到一个“也被借为不可变的” 错误。

现在的解决方案是使用Sum而不是使用fold特质,因为您已经具有默认值(num::Zero)和Add T所需的特征。

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;
    }
}

Playground

答案 1 :(得分:3)

看来问题出在Sum特征的生命周期参数上,这是不删除该特征的解决方案

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);
    }
}

即,通过为特征Sum指定一个独立的生存期参数,就不会假定参数'g会随整个函数一起携带。

答案 2 :(得分:1)

我发现的解决方案是不使用std::iter::Sum并使用sum重写fold调用:

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的生存期,并且编译器很高兴。