3

Say I have the following,

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

#[derive(Debug)]
enum FooReadError {
    UnexpectedEof,
    IoError(io::Error),
}

impl From<io::Error> for FooReadError {
    fn from(err: io::Error) -> FooReadError {
        FooReadError::IoError(err)
    }
}

fn read_n_bytes_to_vector<R: Read>(reader: &mut R, length: usize)
        -> Result<Vec<u8>, FooReadError> {
    let mut bytes = Vec::<u8>::with_capacity(length);
    unsafe { bytes.set_len(length); }
    let bytes_read = try!(reader.read(&mut bytes[..]));
    if bytes_read != length {
        Err(FooReadError::UnexpectedEof)
    } else {
        Ok(bytes)
    }
}

fn do_some_read(reader: &mut Read) -> Vec<u8> {
    read_n_bytes_to_vector(reader, 16).unwrap()
}

fn main() {
    let v = vec![0, 1, 2, 3, 4, 5];
    let mut cur = io::Cursor::<Vec<u8>>::new(v);
    do_some_read(&mut cur);
}

The read_n_bytes_to_vector is supposed to take anything implementing the trait io::Read, read length bytes from it, and put them into a vector and return the vector.

The function do_some_read has a io::Read trait object. So, why then:

% rustc ./vec_read.rs
./vec_read.rs:29:5: 29:27 error: the trait `core::marker::Sized` is not implemented for the type `std::io::Read` [E0277]
./vec_read.rs:29     read_n_bytes_to_vector(reader, 16).unwrap()
                     ^~~~~~~~~~~~~~~~~~~~~~
./vec_read.rs:29:5: 29:27 note: `std::io::Read` does not have a constant size known at compile-time
./vec_read.rs:29     read_n_bytes_to_vector(reader, 16).unwrap()
                     ^~~~~~~~~~~~~~~~~~~~~~

I agree with the compiler that io::Read can't possibly implement Sized; but I'm passing a trait object — those are constant-size, so it should be okay here; **so why the error?* But wait, why does it even matter? The function isn't taking an io::Read for an arg (right?), it's taking a trait object too, because the arg is generic, and should take the full type of what's passed in.

Thanatos
  • 42,585
  • 14
  • 91
  • 146

1 Answers1

4

Generics include the Sized bound by default; if you do not wish it to be required, you must add the ?Sized bound.

A trait object is not of constant size; u16 as Trait is two bytes, u32 as Trait is four bytes, &c.; only things like boxed trait objects (Box<Trait>) and trait object references (&Trait, &mut Trait) have a constant size, known at compile time (two words for the cited examples).

Because you only use an R by mutable reference, you can add the ?Sized bound successfully:

fn read_n_bytes_to_vector<R: ?Sized + Read>(reader: &mut R, length: usize)
        -> Result<Vec<u8>, FooReadError> {
Chris Morgan
  • 86,207
  • 24
  • 208
  • 215
  • Reading [this](https://doc.rust-lang.org/book/trait-objects.html), which says, "Trait objects, like `&Foo` or `Box`" lead me to believe that they were a constant size (the size of two pointers, one for the object, one for the vtable). The docs there show the `foo_obj as &Trait` coercion, but not `foo_obj as Trait`. What does the latter do differently? Also, aren't I using the `&Foo`, which you say *is* constant size, so why do I need `?Sized`? (Nonetheless, your solution works, but I can't explain why, I guess?) – Thanatos Jul 25 '15 at 02:05
  • [This answer](https://stackoverflow.com/questions/28044231/what-does-sized-is-not-implemented-mean) seems to say similar things. Correct me if I'm wrong (testing if I understand); remembering that `Sized` is part of the generic's bound by default (like you say), `R` in my example matches _only_ `Read` in `&mut Read` (the `&mut` not being part of the bound, but part of _that specific use of R_ in the arg); so `R` is `Read`, but `R` needs to satisfy `Sized + Read`, which `Read` of course isn't. Thus, we need to opt-out with `?Sized`. Still curious what/if `foo_type as Trait` does, if anything? – Thanatos Jul 25 '15 at 02:16
  • (Ick, that documentation is very sloppy about the term “trait objects”.) Unsized types cannot be instantiated directly; they can only be created through some form of indirection, such as `Box` and `&T`, because the sizes vary. `foo as Trait` thus doesn’t actually compile; I’m using it to express the concept of what is being pointed *to*. It’s a bit of a complex subject, really. – Chris Morgan Jul 25 '15 at 04:34