Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BufMut implementation for &mut [u8] slices is a footgun #387

Open
phil-opp opened this issue May 8, 2020 · 4 comments
Open

BufMut implementation for &mut [u8] slices is a footgun #387

phil-opp opened this issue May 8, 2020 · 4 comments

Comments

@phil-opp
Copy link

phil-opp commented May 8, 2020

Consider code like this:

use bytes::BufMut;

fn main() {
    let mut buf = Vec::new();
    buf.put_u8(42);
    print_slice(&buf, 1);
}

fn print_slice(slice: &[u8], len: usize) {
    println!("{:?}", &slice[..len]);
}

We create a new Vec based buffer, write an u8 to it, and then print the buffer, which results in the output [42]. (In reality we would for example send it over a network interface instead of printing it.)

Now we want to switch to a stack based buffer (e.g. to avoid the allocation). We see that BufMut is implemented for &mut [u8], so we can just change a single line of code:

-    let mut buf = Vec::new();
+    let mut buf: &mut [u8] = &mut [0; 10];

Instead of using a vector, we now use a stack allocated array with capacity 10 as buffer. Unfortunately, this silently broke our code: The print_slice function now prints [0], even though we wrote the byte 42 to the buffer. In reality, this might mean that we suddenly only send packets containing zeros over the network, which is not good.

(Try it on the playground)

So what's going here? The problem is that put_u8 (and all other BufMut methods) behave differently for Vec and &mut [u8]. For Vec only the len attribute is increased to advance the internal cursor, which doesn't affect the slice that the vector derefs to. For &mut [u8], there is no length attribute, so the BufMut implementation advances the start address of the slice for every cursor movement, which changes the underlying slice.

I'm not sure about the best solution to this problem, but I just spent some considerable time debugging an error caused by this, so I thought that I should at least report it.

@seanmonstar
Copy link
Member

I believe the same situation exists if you use std::io::Write.

@phil-opp
Copy link
Author

phil-opp commented May 8, 2020

Hmm, you're right: https://doc.rust-lang.org/stable/std/io/trait.Write.html#impl-Write-12. I still think that this implementation isn't a good idea, but I understand if you want to follow the implementation of the standard library. Maybe we could at least document this behavior better?

@carllerche carllerche added this to the v0.6 milestone Oct 16, 2020
@carllerche
Copy link
Member

I think we should model std here. I will leave the issue open to track improving the docs.

@carllerche carllerche removed this from the v0.6 milestone Dec 15, 2020
@Ralith
Copy link

Ralith commented Dec 15, 2020

I find the current behavior useful as well. It's pretty weird to write let mut buf = &mut [0; 10] instead of let mut buf = [0; 10] and then pass a temporary &mut.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants