Jos van den Oever
Jos van den Oever

Reputation: 31

How to use lifetimes for nesting mutable access?

I'm writing code in Rust for parsing streams, trait Stream. The streams can consist of other streams. The trait StreamIterator gives access to the substreams. This is when parsing tar files, zip files and other files that contain files.

While writing this code, I've been unsuccessfully fighting the borrow checker.

The code below is a simplified example. In main a file is opened as a stream. That stream is passed to the analyze function which tries to open the stream as a TarStreamIterator to iterate of the streams in the tar. Each embedded stream is also analyzed.

I think that I might to introduce a second lifetime in the StreamIterator trait.

use std::io;
trait Stream<T> {
    fn read(&mut self) -> io::Result<&[T]>;
}
trait StreamIterator<'a,T,S: Stream<T>> {
    fn next(&'a mut self) -> Option<io::Result<S>>;
}
struct FileStream {
}
impl<T> Stream<T> for FileStream {
    fn read(&mut self) -> io::Result<&[T]> {
        Err(io::Error::new(io::ErrorKind::UnexpectedEof, ""))
    }
}
struct TarStream<'a> {
    stream: &'a mut Stream<u8>
}
impl<'a> Stream<u8> for TarStream<'a> {
    fn read(&mut self) -> io::Result<&[u8]> {
        self.stream.read()
    }
}
struct TarStreamIterator<'a> {
    stream: &'a mut Stream<u8>
}
impl<'a> StreamIterator<'a,u8,TarStream<'a>> for TarStreamIterator<'a> {
    fn next(&'a mut self) -> Option<io::Result<TarStream>> {
        // todo: read tar header
        Some(Ok(TarStream{stream: self.stream}))
    }
}
fn analyzeAsTar(s: &mut Stream<u8>) -> bool {
    let mut tar = TarStreamIterator{stream: s};
    while let Some(Ok(mut substream)) = tar.next() {
        analyze(&mut substream);
    }
    true
}
fn analyze(s: &mut Stream<u8>) -> bool {
    analyzeAsTar(s)
}
fn main() {
    let mut fs = FileStream{};
    analyze(&mut fs);
}

This gives this error:

<anon>:38:41: 38:44 error: cannot borrow `tar` as mutable more than once at a time [E0499]
<anon>:38     while let Some(Ok(mut substream)) = tar.next() {
                                                  ^~~
<anon>:38:41: 38:44 help: see the detailed explanation for E0499
<anon>:38:41: 38:44 note: previous borrow of `tar` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `tar` until the borrow ends
<anon>:38     while let Some(Ok(mut substream)) = tar.next() {
                                                  ^~~
<anon>:42:2: 42:2 note: previous borrow ends here
<anon>:36 fn analyzeAsTar(s: &mut Stream<u8>) -> bool {
          ...
<anon>:42 }

Upvotes: 2

Views: 89

Answers (1)

Jos van den Oever
Jos van den Oever

Reputation: 31

There is a workaround. Instead of having a trait with a next() function, one can use a trait with an iterate function. In the example below, TarStreamIterator has a function iterate can accept a closure. (Alternatively, iterator could be called for_each.)

The implementation still has a next function, but the borrow checker accepts this form.

This short example does not actually do anything with the streams.

use std::io;
// generic version of std::io::Read
trait Stream<T> {
    fn read(&mut self) -> io::Result<&[T]>;
}
trait StreamIterator<T> {
    // call `f` on each of the streams in the iterator
    fn iterate<F>(&mut self, mut f: F) where F: FnMut(&mut Stream<T>);
}
struct FileStream {
}
impl<T> Stream<T> for FileStream {
    fn read(&mut self) -> io::Result<&[T]> {
        Err(io::Error::new(io::ErrorKind::UnexpectedEof, ""))
    }
}
struct TarStream<'a> {
    stream: &'a mut Stream<u8>
}
impl<'a> Stream<u8> for TarStream<'a> {
    fn read(&mut self) -> io::Result<&[u8]> {
        self.stream.read()
    }
}
struct TarStreamIterator<'a> {
    stream: &'a mut Stream<u8>
}
impl<'a> TarStreamIterator<'a> {
    // pass the next embedded stream or None if there are no more
    fn next(&mut self) -> Option<TarStream> {
        Some(TarStream{stream: self.stream})
    }
}
impl<'a> StreamIterator<u8> for TarStreamIterator<'a> {
    fn iterate<F>(&mut self, mut f: F) where F: FnMut(&mut Stream<u8>) {
        while let Some(mut substream) = self.next() {
            f(&mut substream);
        }
    }
}
fn analyze_as_tar(stream: &mut Stream<u8>) {
    TarStreamIterator{stream: stream}.iterate(|substream| {
        analyze(substream);
    });
}
fn analyze(s: &mut Stream<u8>) {
    analyze_as_tar(s)
}
fn main() {
    let mut fs = FileStream{};
    analyze(&mut fs);
}

Upvotes: 1

Related Questions