I've defined a trait as follows:
trait Readable<E> {
fn read_u8(&mut self) -> Result<u8, E>;
fn read_u16be(&mut self) -> Result<u16, E>;
}
The idea is to implement it with different backends returning different error types. I tried to use it in a function:
fn f<E, R: Readable<E>>(r: &mut R) -> Result<u8, E> {
r.read_u8()
}
This compiles. I tried the same in an impl:
struct FileFormat<R> {
r: R,
}
impl<E, R: Readable<E>> FileFormat<R> {
fn f(&mut self) -> Result<u8, E> {
self.r.read_u8()
}
}
This fails with:
14 | impl<E, R: Readable<E>> FileFormat<R> {
| ^ unconstrained type parameter
The compiler suggests rustc --explain E0207
but I'm afraid I haven't been able to understand the answer contained within if it's there.
Why does the former compile while the latter doesn't? Why is E
in this case is unconstrained? How to resolve this so that the implementation will be able to take any Readable
?
Let's imagine for a moment that it did compile. Here's a commented example which shows it would allow us to write broken code which would be impossible for the compiler to type check:
trait Readable<E> {
fn read_u8(&mut self) -> Result<u8, E>;
fn read_u16be(&mut self) -> Result<u16, E>;
}
fn f<E, R: Readable<E>>(r: &mut R) -> Result<u8, E> {
r.read_u8()
}
struct SomeError;
struct SomeOtherError;
struct SomeReadable;
impl Readable<SomeError> for SomeReadable {
fn read_u8(&mut self) -> Result<u8, SomeError> {
todo!()
}
fn read_u16be(&mut self) -> Result<u16, SomeError> {
todo!()
}
}
impl Readable<SomeOtherError> for SomeReadable {
fn read_u8(&mut self) -> Result<u8, SomeOtherError> {
todo!()
}
fn read_u16be(&mut self) -> Result<u16, SomeOtherError> {
todo!()
}
}
struct FileFormat<R> {
r: R,
}
// let's pretend that this does compile
impl<E, R: Readable<E>> FileFormat<R> {
fn f(&mut self) -> Result<u8, E> {
self.r.read_u8()
}
}
// it will now allow us to write this code
// which is impossible to type check so
// it's obviously broken
fn example(mut fr: FileFormat<SomeReadable>) -> Result<u8, ???> {
// um, does this return Result<u8, SomeError>
// or does it return Result<u8, SomeOtherError>???
// it's impossible to know!
fr.f()
}
The error type needs to be present somewhere within the FileFormat
type. The fix is as simple as adding a PhantomData
member to FileFormat
so you can "pin down" a specific error type:
use core::marker::PhantomData;
trait Readable<E> {
fn read_u8(&mut self) -> Result<u8, E>;
fn read_u16be(&mut self) -> Result<u16, E>;
}
fn f<E, R: Readable<E>>(r: &mut R) -> Result<u8, E> {
r.read_u8()
}
struct SomeError;
struct SomeOtherError;
struct SomeReadable;
impl Readable<SomeError> for SomeReadable {
fn read_u8(&mut self) -> Result<u8, SomeError> {
todo!()
}
fn read_u16be(&mut self) -> Result<u16, SomeError> {
todo!()
}
}
impl Readable<SomeOtherError> for SomeReadable {
fn read_u8(&mut self) -> Result<u8, SomeOtherError> {
todo!()
}
fn read_u16be(&mut self) -> Result<u16, SomeOtherError> {
todo!()
}
}
struct FileFormat<R, E> {
r: R,
e: PhantomData<E>,
}
// now compiles!
impl<E, R: Readable<E>> FileFormat<R, E> {
fn f(&mut self) -> Result<u8, E> {
self.r.read_u8()
}
}
// now works!
fn example(mut fr: FileFormat<SomeReadable, SomeError>) -> Result<u8, SomeError> {
fr.f()
}
// now also works!
fn other_example(mut fr: FileFormat<SomeReadable, SomeOtherError>) -> Result<u8, SomeOtherError> {
fr.f()
}
The standalone generic function works because we specific the Error type when we call the function:
trait Readable<E> {
fn read_u8(&mut self) -> Result<u8, E>;
fn read_u16be(&mut self) -> Result<u16, E>;
}
struct SomeError;
struct SomeOtherError;
struct SomeReadable;
impl Readable<SomeError> for SomeReadable {
fn read_u8(&mut self) -> Result<u8, SomeError> {
todo!()
}
fn read_u16be(&mut self) -> Result<u16, SomeError> {
todo!()
}
}
impl Readable<SomeOtherError> for SomeReadable {
fn read_u8(&mut self) -> Result<u8, SomeOtherError> {
todo!()
}
fn read_u16be(&mut self) -> Result<u16, SomeOtherError> {
todo!()
}
}
fn f<E, R: Readable<E>>(r: &mut R) -> Result<u8, E> {
r.read_u8()
}
fn example() {
let mut readable: SomeReadable = SomeReadable;
// error type clarified to be SomeError here
f::<SomeError, _>(&mut readable);
let mut readable: SomeReadable = SomeReadable;
// error type clarified to be SomeOtherError here
f::<SomeOtherError, _>(&mut readable);
}
It really all just comes down to making your types visible to the compiler.