在具有多个生命周期约束的类型上实现索引

2 lifetime rust

我遇到了Index在具有生命周期约束的类型上实现的问题.我有一个SubImage结构,其中包含对a的引用Image.我找不到任何满足编译器的方法.

    impl<'a, P> Index<usize> for SubImage<'a, P> {
      type Output = [P];

      fn index<'b> (&'b self, y: usize) -> &'b [P] {
        let start = (self.rect.y0 + y) * self.image.size.x + self.rect.x0;
        let end = start + self.rect.width();
        &self.image.buf[start..end]
      }
    }
Run Code Online (Sandbox Code Playgroud)

'a是引用图像的生命周期,因此切片其缓冲区需要此约束.这里的代码编译,但它是模糊的.对索引运算符的所有调用都会产生错误消息,例如:

    src/image.rs:179:13: 179:32 error: cannot infer an appropriate lifetime for lifetime parameter 'a in function call due to conflicting requirements
    src/image.rs:179       Some(&self.sub[self.back])
                                 ^~~~~~~~~~~~~~~~~~~
    src/image.rs:174:3: 181:4 help: consider using an explicit lifetime parameter as shown: fn next_back(&'a mut self) -> Option<&'a [P]>
    src/image.rs:174   fn next_back (&mut self) -> Option<&'a [P]> {
    src/image.rs:175     if self.front == self.back {
    src/image.rs:176       None
    src/image.rs:177     } else {
    src/image.rs:178       self.back -= 1;
    src/image.rs:179       Some(&self.sub[self.back])
Run Code Online (Sandbox Code Playgroud)

是否有任何可能的方法来确保返回值被限制为两者'a和/ 'b或某种其他方式Index在这种情况下正确实现?编译器的建议不起作用,因为函数签名与特征的签名不匹配.

jte*_*epe 5

实际上你的问题更多,而不是Index trait实现.此外,您的示例并非真正的最小,完整且可验证的示例(MCVE),因此我必须猜测您的确切问题在这里.

你的问题的核心是,你不能让你的迭代器返回一个引用,如果它拥有内容而不借用迭代器本身.你为SubImage实现Index特性是好的.

我会尝试模拟你的问题.假设我们有一个结构Julmond并且它借用了一些整数(类似于你的SubImage).

struct Julmond<'a>(&'a [i32]);

impl<'a> Index<usize> for Julmond<'a> {
    type Output = [i32];

    fn index<'b>(&'b self, idx: usize) -> &'b [i32] {
        if idx < self.0.len() {
            &self.0[idx..] // we always take a subslice from idx until the end
        } else {
            panic!("Index out of bounds!")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

指数特征要求我们借用自我.这很好,因为一些实现者可能拥有您正在索引的数据.这种自我借用是通过在特征方法签名中链接自己的命名生命周期和传出引用来表示的:

fn index(&'a self, index: Idx) -> &'a Self::Output;
Run Code Online (Sandbox Code Playgroud)

如果我们索引到Julmond,只要我们坚持将结果引用到Julmond中,那么该值就被认为是借用的:

let array = [1, 2, 3, 4, 5, 6];
let mut j = Julmond(&array);

let r = &j[3];

&mut j; // Error: r is still in scope and therefore j is still borrowed
Run Code Online (Sandbox Code Playgroud)

我可以从您的示例代码中读到的是,您拥有一些拥有SubImage并实现Iterator特征的类型.我们将尝试模仿另一个结构Nebelung在路上实现Iterator特性:

struct Nebelung<'a> {
    j: Julmond<'a>,
    pos: usize,
}

impl<'a> Iterator for Nebelung<'a> {
    type Item = &'a [i32];

    fn next(&mut self) -> Option<&'a [i32]> {
        if self.pos < self.j.0.len() {
            let tmp_pos = self.pos;
            self.pos += 1;
            Some(&self.j[tmp_pos]) // problematic line
        } else {
            None
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

此实现从基础Julmond结构返回一个不断缩小的数组切片.我们可以这样测试:

fn main() {
    let array = [1, 2, 3, 4, 5, 6];
    let j = Julmond(&array);
    let n = Nebelung { j: &j, pos: 0 };

    for s in n {
        println!("{:?}", s);
    }
}
Run Code Online (Sandbox Code Playgroud)

但这不起作用.编译器会抱怨(如你的例子中)它无法推断'a'的适当生命周期.原因是在索引方法中借用了self.当我们用j [tmp_pos]调用索引运算符时,我们正在借用j.但是j由Nebelung类型的自己拥有,因此从j借用意味着我们从自己借用.我们试图返回对自己拥有的东西的引用,并且要求自己也必须借用.编译器建议正确的事情:链接self和传出引用的生命周期.但是,这违反了下一个方法签名.

如果我们想从迭代器返回一个引用,那么迭代器不能拥有返回的值.否则,我们将不得不在调用中借用迭代器,但接下来是不可能的.

解决这个问题的唯一方法是让迭代器不拥有该值.所以我们修改了Nebelung结构来保存对Julmond的引用:

struct Nebelung<'a: 'b, 'b> {
    j: &'b Julmond<'a>,
    pos: usize,
}
Run Code Online (Sandbox Code Playgroud)

'a:'b表示"'a outlives'b",这里需要它.由于我们对Julmond的引用j不得超过Julmond的借用内容.好的,我们的Nebelung不再是Julmond的老板了.只是一个借款人.现在我们可以像这样实现Iterator特性:

impl<'a, 'b> Iterator for Nebelung<'a, 'b> {
    type Item = &'b [i32];

    fn next(&mut self) -> Option<&'b [i32]> {
        if self.pos < self.j.0.len() {
            let tmp_pos = self.pos;
            self.pos += 1;
            Some(&self.j[tmp_pos])
        } else {
            None
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

自我和传出引用的生命周期不需要链接,因为我们只是返回一些我们不是所有者的值的引用.所以对&self.j [tmp_pos]的调用不再是对自己的借用了.这是从Julmond借来的(通过索引实现).

完整的例子

无论你为什么类型实现Iterator特性.如果类型拥有该值,则不能让next(或next_back)返回引用.让您的类型借用SubImage.