Can you control borrowing a struct vs borrowing a field?

耗尽温柔 提交于 2021-01-02 12:42:09

问题


I'm working on a program involving a struct along these lines:

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

In brief the data field holds some bytes and overlay is a series of byte sequences to be inserted at specific indices. The Sink type is unimportant except that it has a function like:

impl Sink {
    fn process<'a>(&mut self, input: Vec<&'a [u8]>) {
        // ...
    }
}

I've implemented an iterator to merge the information from data and overlay for consumption by 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];
    // ...
}

This is I think a slight lie, because the lifetime of each &[u8] returned by the iterator isn't always that of the original data. The data inserted from overlay has a different lifetime, but I don't see how I can annotate this more accurately. Anyway, the borrow checker doesn't seem to mind - the following approach works:

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

I end up using this merge function all over the place, but always against the same data/overlay. So I figure I'll add an App::merge function for convenience, and here's where the problem begins:

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 now fails to pass the borrow checker - it refuses to allow the mutable borrow of self.sink while self is borrowed.

I've wrestled with this for some time, and if I've understood correctly the problem isn't with process but with this signature:

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

Here I've essentially told the borrow checker that the references returned in the vector are equivalent to the self borrow.

Even though I feel like I've now understood the problem, I still feel like my hands are tied. Leaving the lifetime annotations out doesn't help (because the compiler does the equivalent?), and with only the two references involved there's no way I can see to tell rust that the output reference has a lifetime bound to something else.

I also tried this:

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

but the compiler complains about the let statement ("unable to infer appropriate lifetime due to conflicting requirements" -- I also find it infuriating that the compiler doesn't explain said requirements).

Is it possible to achieve this? The Rust Reference is kind of light on lifetime annotations and associated syntax.

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


回答1:


Yes, you've guessed correctly - the error happens because when you have merge method accept &self, the compiler can't know at its call site that it uses only some fields - merge signature only tells it that the data it returns is somehow derived from self, but it doesn't tell how - and so the compiler assumes the "worst" case and prevents you from accessing other fields self has.

I'm afraid there is no way to fix this at the moment, and I'm not sure there ever will be any. However, you can use macros to shorten merge invocations:

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



回答2:


As long as the method merge takes &self, you cannot accomplish what you desire: it borrows all of each of its arguments and this cannot be altered.

The solution is to change it so that it doesn’t take self, but instead takes the individual fields you wish to be borrowed:

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


来源:https://stackoverflow.com/questions/29896672/can-you-control-borrowing-a-struct-vs-borrowing-a-field

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!