为什么我没有引用数据却得到 "cannot move out of ... because it is borrowed"?

Why do I get "cannot move out of ... because it is borrowed" although no data is referenced?

struct Population<'a> {
    dat: Vec<Genotype<'a>>,
}

impl<'a> Population<'a> {
    fn new(dat: Vec<Genotype<'a>>) -> Self {
        Population { dat }
    }

    fn select(&self) -> Genotype {
        self.dat.first().unwrap().clone()
    }
}

#[derive(Clone)]
struct Genotype<'a> {
    data: &'a str,
}

impl<'a> Genotype<'a> {
    fn new(data: &'a str) -> Self {
        Genotype { data }
    }
}

fn main() {
    let hello = "Hello World";
    let genotype = Genotype::new(hello);
    let mut population = Population::new(vec![genotype]);

    let other = population.select();
    drop(population);
    println!("{}", other.data);
}

Playground

编译器声称population不能删除,因为它是在other中借用的:

error[E0505]: cannot move out of `population` because it is borrowed
  --> src/main.rs:32:10
   |
31 |     let other = population.select();
   |                 ------------------- borrow of `population` occurs here
32 |     drop(population);
   |          ^^^^^^^^^^ move out of `population` occurs here
33 |     println!("{}", other.data);
   |                    ---------- borrow later used here

我看不出这两个变量之间有什么关系。我怀疑错误出在 select 函数中,因为这显然是借用的来源。

我曾尝试向 select (fn select<'b>(&self) -> Genotype<'b>) 添加一个单独的生命周期,但失败了,因为我认为编译器假定 Population 实例的生命周期以某种方式与返回的 Genotype 即使它与内部 &str.

相关

我到底做错了什么?

I suspect the error to be in the select function as this is apparently the source of the borrow.

是的。

the compiler assumes that the lifetime of the Population instance is somehow linked with the returned Genotype

是的,你的代码就是这么说的。

even though it relates to the inner &str

select 的函数签名的哪一部分表明了这一点?没有特别说什么,你依赖 lifetime elision,所以你的函数签名是一样的:

fn select<'x>(&'x self) -> Genotype<'x>

换句话说,就是:“我正在返回一个 Genotype,其中包含一个只要 &self 有效就保证有效的引用”。

相反,您可能想要:

fn select(&self) -> Genotype<'a>

换句话说,就是:“我正在返回一个 Genotype,其中包含一个引用,只要生命周期 'a 有效,该引用就保证有效”。


我强烈建议大家将 #![deny(rust_2018_idioms)] 添加到每个 crate 的根目录中。对于像 -> Genotype 这样的代码,这将导致编译器错误,促使程序员考虑究竟将哪个生命周期放在那里是合适的。

多年的经验表明,允许生命周期省略应用于内部具有生命周期的结构是一个糟糕的选择,而这种 lint 有助于改善这种情况。