Idiomatic way for passing an optional, mutable reference around?


#1

I’m new to Rust and I’m working on an application that serializes some data into a byte buffer. There are several methods that take an &mut Option<&mut Buffer> and some data and return Result<usize, String>. If some buffer is given, the data is written to the buffer and the number of bytes written is returned; if None is given, the functions just return the number of bytes that would have written. See below for a simplified example.

Constructs such as &mut None and &mut Some(...) look a bit awkward to me, esp. since the Option itself is never modified (i.e. cannot be modified), just it’s contents. But I understand why &Option<&mut Buffer> does not work, since it would allow multiple immutable references to the Option and therefore the contained mutable reference to the Buffer.

Is there a “better” or more idiomatic way to pass an optional, mutable Buffer reference through the code?

The much simplified example: https://is.gd/0X2EHv

type Buffer = Vec<u8>;

fn foo(buf: &mut Option<&mut Buffer>) -> Result<usize, String> {
    let mut size: usize = 0;
    size += try!(pack(buf, 1));
    size += try!(pack(buf, 2));
    size += try!(pack(buf, 3));
    Ok(size)
}

fn pack(buf: &mut Option<&mut Buffer>, val: u8) -> Result<usize, String> {
    if let &mut Some(ref mut buf) = buf {
        buf.push(val);
    }
    Ok(1)
}

fn main() {
    let mut buf = vec![];
    let estimate = foo(&mut None).unwrap();
    let actual = foo(&mut Some(&mut buf)).unwrap();
    assert_eq!(estimate, actual);
    println!("{} bytes written: {:?}", actual, buf);
}

The actual implementation of the Buffer type is more than just a byte vector but I think is irrelevant to the question.


#2

I think you want just Option<&mut Buffer>. You don’t want to change the option itself, you want to use the underlying buffer.


#3

If I just use Option<&mut Buffer> then a call like try!(pack(buf, 1)); passes ownership of the Option to the pack method and I cannot use buf any more in foo after that.


#4

Weird, I was sure that Option<&mut Something> is copy! Thanks for clearing this up. Then, I think &mut Option<Buffer> should work?

struct Buff;

fn pack(b: &mut Option<Buff>) { }

fn main() {
    let mut buff: Option<Buff> = Some(Buff);
    pack(&mut buff);
    pack(&mut buff);
}

#5

If &mut T was Copy, you’d have two mutable pointers to the same thing, which would alias them, which is wrong by definition.


#6

Hm, how does

fn by_ref(s: &mut S) {}

fn main() {
    let mut s = S;
    {
        let r = &mut s;
        by_ref(r);
        by_ref(r);
    }
}

work then?


#7

And why this sort of works?

struct S;

fn by_opt_ref(s: Option<&mut S>) {}

fn foo(r: Option<&mut S>) {
    if let Some(r) = r {
        by_opt_ref(Some(r)); 
        by_opt_ref(Some(r));

        let l = Some(r); 
        by_opt_ref(l);

        // Next one will fail.
        // Why the first two work then?
        // by_opt_ref(Some(r)); 
    }
}

fn main(){}

?


#8

I believe this is reborrowing.

Note the help for the error here:

   = note: move occurs because `r` has type `&mut S`, which does not implement the `Copy` trait

One reason why this is safe but being Copy isn’t: this code doesn’t let you have access to the other pointer while you’re inside the function.


#9

See: Reborrow vs move


#10

You could pass an io::Write instead of a Buffer and create a special writer that just counts the bytes and discards them, like:

fn foo<W: std::io::Write>(buf: &mut W) -> std::io::Result<()> {
    ...
}

struct Counter {
    pub count: u64,
}

impl std::io::Write for Counter {
    fn write(&mut self, buf: &[u8]) -> Result<usize> { self.count += buf.len(); Ok(buf.len()) }
    fn flush(&mut self) -> Result<()> {}
}

fn main() {
    let mut counter = Counter { count: 0 };
    let mut buf = vec![];
    foo(&mut counter).unwrap();
    foo(&mut buf).unwrap();
    assert_eq!(counter.count, buf.len());
    println!("{} bytes written: {:?}", buf.len(), buf);
}

(untested)


#11

Thanks, @troplin, I was actually thinking about using a counting-only buffer/writer implementation. Will give that a try.