How to read (std::io::Read) from a Vec or Slice?

16,026

Solution 1

While vectors don't support std::io::Read, slices do.

There is some confusion here caused by Rust being able to coerce a Vec into a slice in some situations but not others.

In this case, an explicit coercion to a slice is needed because at the stage coercions are applied, the compiler doesn't know that Vec<u8> doesn't implement Read.


The code in the question will work when the vector is coerced into a slice using one of the following methods:

  • read_4_bytes(&*vec_as_file)
  • read_4_bytes(&vec_as_file[..])
  • read_4_bytes(vec_as_file.as_slice()).

Note:

  • When asking the question initially, I was taking &Read instead of Read. This made passing a reference to a slice fail, unless I'd passed in &&*vec_as_file which I didn't think to do.
  • Recent versions of rust you can also use as_slice() to convert a Vec to a slice.
  • Thanks to @arete on #rust for finding the solution!

Solution 2

std::io::Cursor

std::io::Cursor is a simple and useful wrapper that implements Read for Vec<u8>, so it allows to use vector as a readable entity.

let mut file = Cursor::new(vector);

read_something(&mut file);

And documentation shows how to use Cursor instead of File to write unit-tests!

Working example:

use std::io::Cursor;
use std::io::Read;

fn read_something(file: &mut impl Read) {
    let _ = file.read(&mut [0; 8]);
}

fn main() {
    let vector = vec![1, 2, 3, 4];

    let mut file = Cursor::new(vector);

    read_something(&mut file);
}

From the documentation about std::io::Cursor:

Cursors are typically used with in-memory buffers to allow them to implement Read and/or Write...

The standard library implements some I/O traits on various types which are commonly used as a buffer, like Cursor<Vec<u8>> and Cursor<&[u8]>.


Slice

The example above works for slices as well. In that case it would look like the following:

read_something(&mut &vector[..]);

Working example:

use std::io::Read;

fn read_something(file: &mut impl Read) {
    let _ = file.read(&mut [0; 8]);
}

fn main() {
    let vector = vec![1, 2, 3, 4];

    read_something(&mut &vector[..]);
}

&mut &vector[..] is a "mutable reference to a slice" (a reference to a reference to a part of vector), so I just find the explicit option with Cursor to be more clear and elegant.


Cursor <-> Slice

Even more: if you have a Cursor that owns a buffer, and you need to emulate, for instance, a part of a "file", you can get a slice from the Cursor and pass to the function.

read_something(&mut &file.get_ref()[1..3]);
Share:
16,026
ideasman42
Author by

ideasman42

Updated on June 03, 2022

Comments

  • ideasman42
    ideasman42 almost 2 years

    Vecs support std::io::Write, so code can be written that takes a File or Vec, for example. From the API reference, it looks like neither Vec nor slices support std::io::Read.

    Is there a convenient way to achieve this? Does it require writing a wrapper struct?

    Here is an example of working code, that reads and writes a file, with a single line commented that should read a vector.

    use ::std::io;
    
    // Generic IO
    fn write_4_bytes<W>(mut file: W) -> Result<usize, io::Error>
        where W: io::Write,
    {
        let len = file.write(b"1234")?;
        Ok(len)
    }
    
    fn read_4_bytes<R>(mut file: R) -> Result<[u8; 4], io::Error>
        where R: io::Read,
    {
        let mut buf: [u8; 4] = [0; 4];
        file.read(&mut buf)?;
        Ok(buf)
    }
    
    // Type specific
    
    fn write_read_vec() {
        let mut vec_as_file: Vec<u8> = Vec::new();
    
        {   // Write
            println!("Writing Vec... {}", write_4_bytes(&mut vec_as_file).unwrap());
        }
    
        {   // Read
    //      println!("Reading File... {:?}", read_4_bytes(&vec_as_file).unwrap());
            //                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
            //                               Comment this line above to avoid an error!
        }
    }
    
    fn write_read_file() {
        let filepath = "temp.txt";
        {   // Write
            let mut file_as_file = ::std::fs::File::create(filepath).expect("open failed");
            println!("Writing File... {}", write_4_bytes(&mut file_as_file).unwrap());
        }
    
        {   // Read
            let mut file_as_file = ::std::fs::File::open(filepath).expect("open failed");
            println!("Reading File... {:?}", read_4_bytes(&mut file_as_file).unwrap());
        }
    }
    
    fn main() {
        write_read_vec();
        write_read_file();
    }
    

    This fails with the error:

    error[E0277]: the trait bound `std::vec::Vec<u8>: std::io::Read` is not satisfied
      --> src/main.rs:29:42
       |
    29 |         println!("Reading File... {:?}", read_4_bytes(&vec_as_file).unwrap());
       |                                          ^^^^^^^^^^^^ the trait `std::io::Read` is not implemented for `std::vec::Vec<u8>`
       |
       = note: required by `read_4_bytes`
    

    I'd like to write tests for a file format encoder/decoder, without having to write to the file-system.