Reputation: 97
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?
Upvotes: 2
Views: 1126
Reputation: 5387
Create a new block to ensure that the immutable borrow (source
) is dropped before capture
is mutated:
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);
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.
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(),
}
}
}
Upvotes: 3