Почему `From` автоматически не используется для принудительного определения типа реализации - PullRequest
0 голосов
/ 07 октября 2018

У меня есть две черты:

trait Foo {}
trait Bar {}

struct FooImpl;
impl Foo for FooImpl {}

struct BarImpl;
impl Bar for BarImpl {}

И третий тип, в который я хочу преобразовать:

struct Baz;

trait IntoBaz {
    fn into(self) -> Baz;
}

Я не могу определить две impl с IntoBazпо двум признакам из-за слаженности, поэтому вместо этого я обертываю одну:

struct FooWrapper<F>(F)
where
    F: Sized;

impl<F: Foo + Sized> From<F> for FooWrapper<F> {
    fn from(f: F) -> FooWrapper<F> {
        FooWrapper(f)
    }
}

impl<F: Foo + Sized> IntoBaz for FooWrapper<F> {
    fn into(self) -> Baz {
        Baz
    }
}

И другую не оборачиваю:

impl<B: Bar> IntoBaz for B {
    fn into(self) -> Baz {
        Baz
    }
}

fn do_thing<B: IntoBaz>(b: &B) {}

fn main() {
    do_thing(&BarImpl);
}

Пока все хорошо, но почему нет?Эта линия работает?

fn main() {
    do_thing(&FooImpl);
}

Мотивация

Я пытаюсь добавить поддержку io::Write в библиотеку с поддержкой fmt::Write без внесения критических изменений.

Самым простым способом было бы определить некоторую внутреннюю черту Write, которая покрывает общее поведение, но проблема согласованности означает, что я не могу просто записать From<io::Write> экземпляров во внутреннюю черту.

Я пытался обернуть io::Write экземпляры, чтобы сделать явное приведение, чтобы компилятор расставил приоритеты для более короткого пути и избежал несогласованности, но он не будет автоматически приводиться с использованием экземпляра From.

Ответы [ 2 ]

0 голосов
/ 08 октября 2018

Ответ PeterHall полностью соответствует заданному вопросу.From и Into не означают ничего особенного на уровне типа.

Однако вы, возможно, просто задали вопрос слишком узко.Похоже, вы хотите, чтобы do_thing(&BarImpl) и do_thing(&FooImpl) компилировали и делали "правильные" вещи.Если это все, что вам нужно, есть несколько хитрый альтернативный подход, который может сработать: добавьте параметр типа в IntoBaz и используйте разные типы, чтобы impl s не перекрывался.

trait IntoBaz<T> {
    fn into_baz(self) -> Baz;
}

struct ByFoo;
impl<F: Foo> IntoBaz<ByFoo> for F {
    fn into_baz(self) -> Baz {
        Baz
    }
}

struct ByBar;
impl<B: Bar> IntoBaz<ByBar> for B {
    fn into_baz(self) -> Baz {
        Baz
    }
}

do_thing теперь может быть универсальным для T:

fn do_thing<T, B: IntoBaz<T>>(_: &B) {}

Когда вы вызываете его, если работает только один T, компилятор найдет его автоматически.

fn main() {
    do_thing(&BarImpl);
    do_thing(&FooImpl);
}

Я пытаюсь добавить поддержку io::Write в библиотеку с поддержкой fmt::Write без внесения критических изменений.

К сожалению, это предложение технически является критическим изменением.Если есть некоторый тип, который реализует и io::Write и fmt::Write, то do_thing(&implements_both) (который раньше использовал fmt::Write) теперь не будет компилироваться из-за неоднозначности.Но любое место, где выбор черты однозначен, все равно будет компилироваться, как и раньше, поэтому риск поломки намного ниже.

См. Также:

0 голосов
/ 07 октября 2018

Посмотрите на сообщение об ошибке:

error[E0277]: the trait bound `FooImpl: Bar` is not satisfied
  --> src/main.rs:48:5
   |
48 |     do_thing(&FooImpl);
   |     ^^^^^^^^ the trait `Bar` is not implemented for `FooImpl`
   |
   = note: required because of the requirements on the impl of `IntoBaz` for `FooImpl`
note: required by `do_thing`
  --> src/main.rs:45:1
   |
45 | fn do_thing<B: IntoBaz>(b: &B) {}
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Это говорит о том, что FooImpl не имеет реализации Bar, что является требованием вашей общей IntoBaz for B реализации.

Реализация FooWrapper не имеет значения, поскольку FooImpl отличается от FooWrapper.Черты From и Into обеспечивают способ преобразования между типами, но это не происходит автоматически.

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

Но вы можете определить реализацию IntoBaz только для FooImpl:

impl IntoBaz for FooImpl {
    fn into(self) -> Baz {
        IntoBaz::into(FooWrapper::from(self))
    }
}

Что сделает ваш код скомпилированным:

fn main() {
    do_thing(&BarImpl);
    do_thing(&FooImpl);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...