Как реализовать структуру, которая принимает трейт MyTrait‹A›?

Я определил черту следующим образом:

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 FileFormat<R> {
    r: R,
}

impl<E, R: Readable<E>> FileFormat<R> {
    fn f(&mut self) -> Result<u8, E> {
        self.r.read_u8()
    }
}

Игровая площадка

Это не удается с:

14 | impl<E, R: Readable<E>> FileFormat<R> {
   |      ^ unconstrained type parameter

Компилятор предлагает rustc --explain E0207, но я боюсь, что не смог понять ответ, содержащийся внутри, если он там есть.

Почему первый компилируется, а второй нет? Почему E в этом случае не имеет ограничений? Как это решить, чтобы реализация могла принимать любые Readable?


person puritii    schedule 15.02.2021    source источник
comment
Дубликаты применены к Readable   -  person trentcl    schedule 16.02.2021


Ответы (1)


Давайте на мгновение представим, что он скомпилировался. Вот пример с комментариями, который показывает, что это позволит нам написать неработающий код, который компилятор не сможет проверить:

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

игровая площадка

Тип ошибки должен присутствовать где-то внутри типа FileFormat. Исправление так же просто, как добавление члена PhantomData в FileFormat, чтобы вы могли определить конкретный тип ошибки:

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

игровая площадка

Автономная универсальная функция работает, потому что мы указываем тип ошибки при вызове функции:

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

игровая площадка

На самом деле все сводится к тому, чтобы сделать ваши типы видимыми для компилятора.

person pretzelhammer    schedule 16.02.2021
comment
Имеет смысл. Спасибо. Я пошел по другому пути, как только понял, что отдельный тип E не имеет смысла, потому что SomeReadable не должен иметь разных типов ошибок. Я остановился на связанном типе в Readable, что позволило FileFormat остаться без изменений. Ваш пример указал путь. - person puritii; 16.02.2021