Reputation: 31
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
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