How does Vec, but not [u8], make Cursor have "write()"

I'm trying to write some data into an in-memory buffer and have been playing with the std::io::Cursor.

I noticed that it works as expected with Vec internally, but not with slices. More accurately speaking, Cursor with [u8; _] does get initialized but Rust compiler doesn't seem to find write() method. Could anyone tell me why this is happening?

    let mut buf = vec![0; 1024]; // write() works
    let mut buf = [0u8; 1024]; // write() doesn't work
    
    let mut buf = Cursor::new(buf);
    buf.write(&b"abc"[..]);

The write method comes from an implementation of std::io::Write. If you look at the docs page for cursor and search for "fn write", you should be able to see that there are a bunch of Write impls.

However, none of them are blanket impls. They're all implementations for specific types. For instance,

impl Write for Cursor<&mut Vec<u8>> { .. }
impl Write for Cursor<&mut [u8]> { .. }
impl Write for Cursor<Vec<u8>> { .. }
impl Write for Cursor<Box<u8>> { .. }

Thus, you only get a Cursor::write method if the inner type is one of the ones Cursor implements Write for. Cursor implements Write for an inner type of Vec<u8> and &mut [u8], but not [u8; 1024].

As for why, I would bet this is a remnant of rust originally lacking the generics necessary to implement a trait for [u8; N] of any length. Prior to the Rust 1.51.0 release, implementing this would have required 1024 separate impl blocks, one for each size, and even then it would only have worked for arrays up to size 1024. This wasn't done for many implementations, and it was only done for sizes up to 32 for the impls arrays did have, as more would have significantly bloated the size of the standard library.

I think this impl could probably be added today, honestly. Perhaps no one has thought of doing it since the Rust 1.51.0 made it possible? I searched for related issues, but didn't find anything. I'd bet we could definitely add impl<const N: usize> Write for Cursor<[u8; N]> { .. }, but it just hasn't been done yet.

Regardless, it's not there today, so if you want to use a const-sized array in a cursor, you'll need to either pass a mutable reference (and cast to &mut [u8] rather than &mut [u8; 1024]) and create a Cursor<&mut [u8]>, or box the slice and create a Cursor<Box<[u8]>>.

6 Likes

I.e.

    let mut buf = [0u8; 1024];
    let mut buf = Cursor::new(buf.as_mut());
    let _ = buf.write(&b"abc"[..]);
3 Likes

Really thanks for the detailed explanation. I think I had too naively presumed that features in "std" are every-circumstance-covering out of the box with the necessary blanket implementations for any kind.

I'd most likely take a route of passing a mutable reference of a const-sized slice as you suggested!

You don't really need a Cursor if you don't need to seek. &mut [u8] already implements Write, so you can do:

let mut buf = [0u8; 1024];
let ptr = &mut buf[..];
let _ = ptr.write(b"abc");

The Write impl updates the ptr slice with every write to point at the unused part of the buf.

1 Like

NB, the same is also true for Read which is implemented by &[u8].

1 Like

Yeap, fair point!

I opened a PR implementing Write for Cursor<T> where T: AsMut<[u8]>, which includes arrays.
https://github.com/rust-lang/rust/pull/92663

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.