【问题标题】:How to ensure end of immutable borrow after function call in order to enable mutable borrow?如何确保函数调用后不可变借用结束以启用可变借用?
【发布时间】:2019-12-12 14:41:54
【问题描述】:

我在使用 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 的调用的整个其余部分。

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

【问题讨论】:

    标签: rust


    【解决方案1】:

    看来问题出在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 将不会被假定为整个函数。

    【讨论】:

    • 正是我的想法,并且希望是可能的。非常感谢,这正是我希望的方式解决了我的问题! :)
    【解决方案2】:

    问题在于Sum trait,让我们看看它的declaration

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

    (Playground)

    【讨论】:

    • 我选择了另一个答案作为“解决方案”,因为它让我可以继续使用我更喜欢的 sum,但我非常非常感谢您对问题的清晰解释和建议。谢谢@你好! :)
    【解决方案3】:

    我找到的解决方案是不使用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 的生命周期,编译器很高兴。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-04-15
      • 1970-01-01
      • 1970-01-01
      • 2016-05-02
      • 2020-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多