Hi, I'm struggling with traits :
I'm trying to implement a binary protocol, made of a stream of a variety of fields. I want to be able to read those fields from any Reader. The fields are mostly newtypes over primitive types.
Here's what I implemented, it seems to work well.
trait FieldRead<R: std::io::Read>
where
Self: std::marker::Sized,
{
fn read(buf: R) -> Result<Self>;
}
#[derive(Debug, Copy, Clone, PartialEq, Ord, PartialOrd, Eq)]
struct FieldA(i32);
impl<R: std::io::Read> FieldRead<R> for FieldA {
fn read(mut bytes: R) -> Result<Self> {
/* reads a FieldA */
}
}
// and many more, FieldB, FieldC, etc.
Now i would like to be able to create every field from u8 slices. I wrote this :
trait FieldReadBytes
where
Self: std::marker::Sized,
{
fn from_bytes(bytes: &[u8]) -> Result<Self>;
}
impl FieldReadBytes for FieldA {
fn from_bytes(bytes: &[u8]) -> Result<Self> {
let c = Cursor::new(bytes);
Self::read(c)
}
} // and many more...
but with this solution, I'd have to duplicate that exact same impl for every Field. How can I make every Field automatically implement this ?
I'm still having trouble with the two solutions you proposed.
The default implementation solution complains with the following error :
error[E0308]: mismatched types
--> src/proto.rs:20:20
|
20 | Self::read(c)
| ^ expected type parameter, found struct `std::io::Cursor`
|
= note: expected type `R`
found type `std::io::Cursor<&[u8]>`
= help: type parameters must be constrained to match other types
which I don't really understand : a Cursor is Read, so there shouldn't be any problem here.
Edit : the problem went away by modifying FieldRead as so :
trait FieldRead
where
Self: std::marker::Sized,
{
fn read(buf: impl std::io::Read) -> Result<Self>;
fn from_bytes(bytes: &[u8]) -> Result<Self> {
let c = Cursor::new(bytes);
Self::read(c)
}
}
But I'm afraid I don't really understand the difference between making the read function generic and the whole trait generic.
--
The blanket impl solution expects a type argument for FieldRead, which makes sense in a way, but I don't know what to put there : nothing in the impl or the trait uses it, as far as I know.
Ah I hadn't even noticed that you put R on the trait. Basically the difference is that with the trait being generic, you can have types that implement FieldRead<Cursor<Vec<u8>> but not FieldRead<File>. Each type is free to pick and choose which readers to implement.
When placing it on the read function instead, there's only one FieldRead trait instead of one for every type implementing Read, and to implement it, you must be able to read from anything.