你能控制借用结构体和借用字段吗?

5

我正在开发一个涉及结构体的程序,大致如下:

struct App {
    data: Vec<u8>,
    overlay: Vec<(usize, Vec<u8>)>,
    sink: Sink,
}

简单来说,data字段包含一些字节,overlay是一系列要插入到特定索引处的字节序列。 Sink类型并不重要,除了它有一个类似于以下函数的函数:
impl Sink {
    fn process<'a>(&mut self, input: Vec<&'a [u8]>) {
        // ...
    }
}

我已经实现了一个迭代器,将dataoverlay中的信息合并后,供Sink使用。
struct MergeIter<'a, 'b> {
    data: &'a Vec<u8>,
    overlay: &'b Vec<(usize, Vec<u8>)>,
    // iterator state etc.
}

impl<'a, 'b> Iterator for MergeIter<'a, 'b> {
    type Item = &'a [u8];
    // ...
}

我认为这有点虚假,因为迭代器返回的每个&[u8]的生命周期并不总是与原始data的寿命相同。从overlay插入的数据具有不同的生命周期,但我不知道如何更准确地注释它。无论如何,借用检查器似乎并不介意 - 以下方法可行:

fn merge<'a, 'b>(data: &'a Vec<u8>, overlay: &'b Vec<(usize, Vec<u8>)>, start: usize) -> Vec<&'a [u8]> {
    MergeIter::new(data, overlay, start).collect()
}

impl App {
    fn process(&mut self) {
        let merged = merge(&self.data, &self.overlay, 0);
        // inspect contents of 'merged'
        self.sink.process(merged);
    }
}

我一直在多个地方使用这个merge函数,但总是针对相同的数据/覆盖。因此,我考虑添加一个App::merge函数以方便使用,这时问题开始了:

impl App {
    fn merge<'a>(&'a self, start: usize) -> Vec<&'a [u8]> {
        MergeIter::new(&self.data, &self.overlay, start).collect()
    }

    fn process(&mut self) {
        let merged = self.merge(0);
        // inspect contents of 'merged'
        self.sink.process(merged);
    }
}

App::process 现在不能通过借用检查器 - 它拒绝允许对 self.sink 的可变借用,而此时 self 已经被借用了。

我已经与这个问题纠缠了很长时间,如果我理解正确,问题不在于 process,而在于这个签名:

fn merge<'a>(&'a self, start: usize) -> Vec<&'a [u8]> {

在这里,我基本上告诉了借用检查器,向量返回的引用与self借用是等价的。

即使我现在感觉已经理解了问题,但我仍然感觉手脚无措。省略生命周期注释并没有帮助(因为编译器会执行等效操作?),而且只涉及两个引用,我看不出任何可以告诉rust输出引用有一个生命周期绑定到其他内容的方法。

我还尝试过这个:

fn merge<'a, 'b>(&'b self, start: usize) -> Vec<&'a [u8]> {
    let data: &'a Vec<u8> = &self.data;
    MergeIter::new(&self.data, &self.overlay, start).collect()
}

但编译器抱怨let语句(“由于冲突的要求无法推断出适当的生命周期”——我还觉得编译器没有解释这些要求很让人恼火)。

有可能实现吗?Rust参考手册在生命周期注释和相关语法方面有点薄弱。

rustc 1.0.0-nightly (706be5ba1 2015-02-05 23:14:28 +0000)


1
你上一次的尝试是错误的 - 你实际上是在说'a 可以成为调用者选择的任意生命周期,因此它可能会选择'static。但你的代码并没有返回一个具有静态生命周期的引用,它返回的是一个具有 self.data 生命期的引用。这显然行不通,所以这样的签名被认为是不安全的。顺便说一句,编译器通常会显示冲突要求 - 它会突出显示相关生命周期的作用域。你的其余问题需要进行更多的研究。 - Vladimir Matveev
1
&Vec<T> 可以通过将其更改为 &[T] 来变得更加高效;&Vec<T> 也会自动转换为 &[T],因此在使用它时不会增加负担。 - Chris Morgan
@VladimirMatveev 我看到一些生命周期错误的作用域被突出显示,但在这种特殊情况下没有。这使得它更加令人沮丧 :P - sqweek
2个回答

5
只要方法merge采用&self,你就无法实现你想要的功能:它借用了其每个参数的所有权,这是无法改变的。
解决方案是将其更改为不采用self,而是采用你希望被借用的各个字段。
impl App {
    ...
    fn merge(data: &Vec<u8>, overlay: &Vec<(usize, Vec<u8>)>, start: usize) -> Vec<&[u8]> {
        MergeIter::new(data, overlay, start).collect()
    }

    fn process(&mut self) {
        let merged = Self::merge(&self.data, &self.overlay, 0);
        ... // inspect contents of 'merged'
        self.sink.process(merged);
    }
}

糟糕,&self.data&self.overlay显然应该是dataoverlay - Chris Morgan

2
是的,你猜对了——当你的merge方法接受&self时,错误就会发生。在调用该方法时,编译器无法知道它仅使用了某些字段——merge签名只告诉它返回的数据是从self派生的,但并不说明如何派生,因此编译器假定“最坏”的情况,并防止你访问self具有的其他字段。
恐怕目前没有解决这个问题的办法,我也不确定以后会不会有。但是,您可以使用宏来缩短merge调用:
macro_rules! merge {
    ($this:ident, $start:expr) => {
        MergeIter::new(&$this.data, &$this.overlay, $start).collect()
    }
}

fn process(&mut self) {
    let merged = merge!(self, 0);
    // inspect contents of 'merged'
    self.sink.process(merged);
}

我还没有研究过Rust宏 - 我想现在是时候了! - sqweek

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