puritii
puritii

Reputation: 1309

How do implement a struct that takes a trait MyTrait<A>?

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()
    }
}

Playground

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?

Upvotes: 0

Views: 290

Answers (1)

pretzelhammer
pretzelhammer

Reputation: 15155

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()
}

playground

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()
}

playground

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);
}

playground

It really all just comes down to making your types visible to the compiler.

Upvotes: 2

Related Questions