The trait `std::io::Read` is not implemented for `&'a str`

With the below code I'm getting the error:

the trait bound &'a str: std::io::Read is not satisfied
the trait std::io::Read is not implemented for &'a str

#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub struct ReaderOptions {
    pub stop_after_first_error: bool,
    pub stop_after_eof: bool,
}

impl Default for ReaderOptions {
    fn default() -> Self {
        ReaderOptions {
            stop_after_first_error: true,
            stop_after_eof: true,
        }
    }
}

pub struct Reader<'a, R: Read> {
    line_iterator: ByteLines<BufReader<R>>,
    finished: bool,
    options: ReaderOptions,
    foo: &'a bool,
}

impl <'a> Reader<'a, &'a str> {
    pub fn new_with_options(string: &'a str, options: ReaderOptions) -> Self {
        Reader::new_from_generic_with_options(*string, options)
    }

    pub fn new(string: &'a str) -> Self {
        Reader::new_with_options(string, Default::default())
    }
}

How can I solve this?

Read is implemented for &[u8], so you could use string.as_bytes() to get something that implements Read out of a &str. You can't impl Reader<'a, &'a str> so long as you keep the R: Read bound, though. Your &str-taking methods could be free functions, or you could perhaps drop the R: Read bound on the struct itself, and only use the bound in places where you specifically need it.

You also need to actually supply your new_from_generic_with_options method. It won't be able to take a str (contrast: &str) because a str is unsized (has no static size; takes up a dynamic amount of bytes).

2 Likes

I've tried it now with &[u8] and Cursor to have an in memory object which behaves like a file. But now I get the following error:

mismatched types
expected &[u8], found struct std::io::Cursor

impl <'a> Reader<'a, &'a [u8]> {
    pub fn new_with_options(buffer: &'a [u8], options: ReaderOptions) -> Self {
        let mut file = Cursor::new(buffer);
        Reader::new_from_generic_with_options(file, options)
    }

    pub fn new(buffer: &'a [u8]) -> Self {
        Reader::new_with_options(buffer, Default::default())
    }
}

Then I've tried with:

impl <'a, T> Reader<'a, Cursor<T>> {
    pub fn new_with_options(buffer: &'a [u8], options: ReaderOptions) -> Self {
        let mut file = Cursor::new(buffer);
        Reader::new_from_generic_with_options(file, options)
    }

    pub fn new(buffer: &'a [u8]) -> Self {
        Reader::new_with_options(buffer, Default::default())
    }
}

But now I get the following error:

the trait bound T: std::convert::AsRef<[u8]> is not satisfied
the trait std::convert::AsRef<[u8]> is not implemented for T
note: required because of the requirements on the impl of std::io::Read for std::io::Cursor<T>

Now I'm a bit lost.

You still haven't supplied new_from_generic_with_options but from the errors, I'm going to guess it's something like:

impl <'a, R: 'a + Read> Reader<'a, R> {
    pub fn new_from_generic_with_options(_: R, _: ReaderOptions) -> Self {
        todo!()
    }
}

In which case, the first error is because in this context:


impl <'a> Reader<'a, &'a [u8]> {
    pub fn new_with_options(buffer: &'a [u8], options: ReaderOptions) -> Self

Self is the thing you are implementing -- a Reader<'a, &'a [u8]>. But you're not returning that, you're returning a Reader<'a, Cursor<&'a [u8]>>. You could change the signature, but since returning Self is the point of these methods, you probably want to change what you are implementing instead. Here's a playground that does that. (If you wanted to see what just changing the signature looks like, you can see that in this playground.)

In the second version, you're implementing on Reader<'a, Cursor<T>> where T is unconstrained, but you're still returning a Reader<'a, Cursor<&'a [u8]>>. You could constrain T with T: AsRef<[u8]> to change your trait bound error into a type mismatch error, but it's still an error, because you're still returning a Reader<'a, Cursor<&'a [u8]>>. T might be something that's not &[u8].

So perhaps the confusion is coming in with how generic work. In the second version, when you said:

impl <'a, T> Reader<'a, Cursor<T>> {

You're basically saying, this is an implementation for any type T. The users of this implementation can pick any T they want. You can constrain their choices with trait bounds, like T: AsRef<[u8]>, but they still get to choose the T. So when you say that you're going to return Self, it has to be a Reader<'a, Cursor<T>> -- and that T has to be the one they chose, not a specific type like &[u8].

Here's a playground for that second version. The changes are:

  • I added a bound for T: AsRef<[u8]> + 'a so you can create the Cursor
  • I changed new and new_with_options to take T and not a &[u8]

And now it creates a Cursor<T> and returns a Reader<'a, Cursor<T>>, which matches Self, as required.

2 Likes

There's a shortcut to avoid a Cursor. To read &[u8] you need &mut &[u8] — a reference to a reference. &mut str.as_bytes().

That's because I/O operations need to update the slice to keep track how much of it has already been read, and &[u8] by itself is immutable, but &mut &[u8] allows shortening of the slice.

1 Like