2
votes

I'm trying to implement a zero copy mechanism for realtime data processing in Rust. To illustrate my issue, I prepared the following example:

use std::io;

pub trait Producer<T> {
    fn produce(&self) -> Result<T, ()>;
}

pub trait Consumer<T> {
    fn consume(&self, t: T);
}

pub trait Source<T> : Producer<T> {
    fn push(&self, t: T) -> io::Result<()>;
}

pub trait Sink<T> : Consumer<T> {
    fn pull(&self) -> io::Result<T>;
}

pub struct SyncSource<T> {
    pub producer: Option<Box<dyn Fn() -> T>>,
}

impl<T> SyncSource<T> {
    pub fn new() -> SyncSource<T> {
        SyncSource {
            producer: None,
        }
    }
}

impl<T> Producer<T> for SyncSource<T> {
    fn produce(&self) -> Result<T, ()> {
        match &self.producer {
            Some(func) => Ok((*(func))()),
            None => Err(()),
        }
    }
}

impl<T> Source<T> for SyncSource<T> {
    fn push(&self, t: T) -> io::Result<()> {
        // do something useful
        Ok(())
    }
}

pub struct Frame<'a> {
    pub buf: &'a [u8],
}

pub struct Capture {
    buf: Vec<u8>,
}

impl Capture {
    pub fn add(&mut self, val: u8) {
        self.buf.push(val);
    }

    pub fn read(&self) -> Frame {
        Frame {
            buf: &self.buf[..],
        }
    }
}

fn main() {
    let mut capture = Capture {
        buf: Vec::new(),
    };

    let source: SyncSource<Frame> = SyncSource::new();

    // immutable borrow of 'capture'
    let frame = capture.read();

    source.push(frame);

    // mutable borrow of 'capture'
    capture.add(1); // ERROR
}

.. which of course yields a borrow checker error:

error[E0502]: cannot borrow `capture` as mutable because it is also borrowed as immutable
   --> src/bin/so.rs:212:5
    |
208 |     let frame = capture.read();
    |                 ------- immutable borrow occurs here
...
212 |     capture.add(1);
    |     ^^^^^^^^^^^^^^ mutable borrow occurs here
213 | }
    | - immutable borrow might be used here, when `source` is dropped and runs the destructor for type `SyncSource<'_, Frame<'_>>`

I understand that push(frame) cannot have an immutable reference in the same scope where capture.add(1) needs the mutable reference a few lines later.

What I'm trying to achieve is for push(frame) to be able to do something useful with the slice (and maybe copy it into a Vec if necessary), but with the possibility not to do anything with it.

Basically I need to ensure the lifetime of frame ends once push(frame) has been called. This would then release the borrowed reference to Capture and the capture.add(1) call would succeed with a proper mutable reference being acquired.

My zero-copy requirement mandates not copying the slice into a Vec and then handing that new buffer to push(..). What am I missing here? Perhaps some explicit lifetime annotations?

1
I think in this case simply having let mut owner = ... solves the compilation error. and NLL (non-lexical-lifetimes) should be enough so that your example works as intended: play.rust-lang.org/…phimuemue
Sorry, I accidentally uploaded the wrong code. I updated the post with the proper example now.acr
What happens if you put source, sink, frame in a separate scope? play.rust-lang.org/…phimuemue
Unfortunately that is not an option because I have a while .. loop in the main function which reads a frame, pushes it down the pipeline and then modifies the Capture object. Like this: play.rust-lang.org/….acr
This code is very abstract, which makes it hard to fix because I don't know what fixes might be appropriate or not. I'm guessing the answer to your previous question didn't stick because you're not using &'a mut self anymore?trentcl

1 Answers

1
votes

How to fix it

Create a new block to ensure that the immutable borrow (source) is dropped before capture is mutated:

Playground

let mut capture = Capture {
    buf: Vec::new(),
};

{
    let source: SyncSource<Frame> = SyncSource::new();

    // immutable borrow of 'capture'
    let frame = capture.read();

    // borrow moved into `source`
    source.push(frame);

    // `source` dropped here
}

// mutable borrow of 'capture'
capture.add(1);

Why NLL doesn't help

This problem should be fixed with non-lexical lifetimes (NLL). However, NLL don't work for types that implement the Drop trait, because Drop is always called at the end of a value's lexical scope for backwards compatibility.

Since SyncSource contains a trait object (dyn Fn() -> T), which could potentially implement Drop, NLL is prevented in this case. In this playground you can see that removing the trait object fixes the error thanks to NLL.

But I want to access both source and capture in a loop!

Then mutable and immutable borrows are interleaved, which means that Rust can't verify the ownership rules at compile time.

You can work around this by using RefCell, which ensures that the ownership rules are upheld at runtime. This can be implemented like this:

use std::cell::{RefCell, Ref};

pub struct Frame<'a> {
    pub buf: Ref<'a, Vec<u8>>,
}

pub struct Capture {
    buf: RefCell<Vec<u8>>,
}

impl Capture {
    pub fn add(&self, val: u8) {
        self.buf.borrow_mut().push(val);
    }

    pub fn read(&self) -> Frame {
        Frame {
            buf: self.buf.borrow(),
        }
    }
}