vbezhenar
vbezhenar

Reputation: 12326

Use Option<&mut> 2 times

The following program compiles fine:

fn write_u16(bytes: &mut Vec<u8>, value: u16) {
    bytes.push((value >> 8) as u8);
    bytes.push(value as u8);
}

fn write_u32(bytes: &mut Vec<u8>, value: u32) {
    write_u16(bytes, (value >> 16) as u16);
    write_u16(bytes, value as u16);
}

now I'll change bytes type to Option<&mut Vec>:

fn write_u16(bytes_opt: Option<&mut Vec<u8>>, value: u16) {
    if let Some(bytes) = bytes_opt {
        bytes.push((value >> 8) as u8);
        bytes.push(value as u8);
    }
}

fn write_u32(bytes_opt: Option<&mut Vec<u8>>, value: u32) {
    write_u16(bytes_opt, (value >> 16) as u16);
    write_u16(bytes_opt, value as u16);
}

The program doesn't compile now:

main.rs:10:15: 10:24 error: use of moved value: `bytes_opt`
main.rs:10     write_u16(bytes_opt, value as u16);
                         ^~~~~~~~~
main.rs:9:15: 9:24 note: `bytes_opt` moved here because it has type `core::option::Option<&mut collections::vec::Vec<u8>>`, which is non-copyable
main.rs:9     write_u16(bytes_opt, (value >> 16) as u16);
                        ^~~~~~~~~
error: aborting due to previous error

I don't really understand, why I can't use Option twice and how do I solve this problem?

The only way I can imagine to solve this problem is:

fn write_u32(bytes_opt: Option<&mut Vec<u8>>, value: u32) {
    if let Some(bytes) = bytes_opt {
        write_u16(Some(bytes), (value >> 16) as u16);
        write_u16(Some(bytes), value as u16);
    } else {
        write_u16(None, (value >> 16) as u16);
        write_u16(None, value as u16);
    }
}

but that's not very nice code.

Upvotes: 0

Views: 124

Answers (2)

Chris Morgan
Chris Morgan

Reputation: 90742

Although I suspect in this case you should not be doing this (as commented already), it is possible to reborrow mutable references for this sort of a case where it is safe:

fn write_u32(mut bytes_opt: Option<&mut Vec<u8>>, value: u32) {
    write_u16(bytes_opt.as_mut().map(|x| &mut **x), (value >> 16) as u16);
    write_u16(bytes_opt, value as u16);
}

The bytes_opt.as_mut().map(|x| &mut **x) could also be written match bytes_opt { Some(&mut ref mut x) => Some(x), None => None, }. A pretty mind-bending pattern (read it from left to right: &mut—dereference the contained value, ref mut—and then take a new mutable reference to it), but it works and avoids the ownership issue.

Upvotes: 3

Shepmaster
Shepmaster

Reputation: 430634

The error message is telling you they key things:

bytes_opt moved here because it has type core::option::Option<&mut collections::vec::Vec<u8>>, which is non-copyable

Your function signature states that it is going to consume the argument:

fn write_u16(bytes_opt: Option<&mut Vec<u8>>, value: u16)
//                      ^~~~~~~~~~~~~~~~~~~~

However, by consuming it, it also consumes the mutable reference. If you had another type like Option<u8> or Option<&Vec<u8>>, then the compiler could just insert an implicit copy of the variable for you. However, you aren't allowed to make copies of mutable references, because then you would have mutable aliases, which the compiler disallows for memory-safety reasons.

When you pass just the &mut Vec<u8>, the compiler is able to track the reference and see that only one item has the reference at a time, so it allows it. However, it is unable to track that when the mutable reference is embedded in another type.

To actually get it to work, it's a bit ugly, with more mut qualifiers than I would like:

fn write_u16(bytes_opt: &mut Option<&mut Vec<u8>>, value: u16) {
    if let Some(ref mut bytes) = *bytes_opt {
        bytes.push((value >> 8) as u8);
        bytes.push(value as u8);
    }
}

fn write_u32(mut bytes_opt: Option<&mut Vec<u8>>, value: u32) {
    write_u16(&mut bytes_opt, (value >> 16) as u16);
    write_u16(&mut bytes_opt, value as u16);
}

Prompted by @ChrisMorgan, I got something that fits your original API:

fn write_u16(bytes_opt: Option<&mut Vec<u8>>, value: u16) {
    if let Some(bytes) = bytes_opt {
        bytes.push((value >> 8) as u8);
        bytes.push(value as u8);
    }
}

fn write_u32(bytes_opt: Option<&mut Vec<u8>>, value: u32) {
    if let Some(bytes) = bytes_opt {
        write_u16(Some(bytes), (value >> 16) as u16);
        write_u16(Some(bytes), value as u16);
    }
}

Upvotes: 2

Related Questions