Как реализовать трейт для типов, реализующих другой трейт, без конфликтующих реализаций - PullRequest
1 голос
/ 07 мая 2020

У меня широкая базовая черта. Некоторые типы заботятся только о подмножестве своих функций, поэтому я добавил вычитку, требующую от пользователя реализации меньшего набора функций.

Этот код не работает:

trait Base<T> {
    fn foo(arg: bool);
}

// Ext is a narrowing of Base requiring user to provide alternative, simpler interface for the same functionality
trait Ext<T>: Base<T> {
    fn bar();
}

// implement Base<T> for all types implementing Ext<T>
impl<T, E> Base<T> for E
where
    E: Ext<T>,
{
    fn foo(arg: bool) {
        Self::bar();
    }
}

struct Data<T>;

// error[E0119]: conflicting implementations of trait `Base<_>` for type `Data<_>`:
impl<T> Base<T> for Data<T> {
    fn foo(arg: bool) {}
}

Со следующей ошибкой:

error[E0119]: conflicting implementations of trait `Base<_>` for type `Data<_>`:
  --> src/lib.rs:22:1
   |
11 | / impl<T, E> Base<T> for E
12 | | where
13 | |     E: Ext<T>,
14 | | {
...  |
17 | |     }
18 | | }
   | |_- first implementation here
...
22 |   impl<T> Base<T> for Data<T> {
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Data<_>`
   |
   = note: downstream crates may implement trait `Ext<_>` for type `Data<_>`

Интересно, что это работает, когда я удаляю общие черты T:

trait Base {
    fn foo(arg: bool);
}

// Ext is a narrowing of Base requiring user to provide alternative, simpler interface for the same functionality
trait Ext: Base {
    fn bar();
}

// implement Base for all types implementing Ext
impl<E> Base for E
where
    E: Ext,
{
    fn foo(arg: bool) {
        Self::bar();
    }
}

struct Data;

// works just fine
impl Base for Data {
    fn foo(arg: bool) {}
}

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

По сути, ошибка упоминает downstream crates may implement trait 'Ext<_>' for type 'Data<_>', что неверно, потому что и Ext, и Data было бы чужеродным для этих ящиков.

Подводя итог, мои вопросы:

  1. Почему мое одеяло impl отклонено, хотя это кажется невозможным для других ящиков для создания коллизии.
  2. Почему версия без T не отклоняется, несмотря на то, что в основном это тот же самый бланкет?
  3. Есть ли способ решения этой проблемы?

1 Ответ

1 голос
/ 08 мая 2020

Причина, по которой вы видите ошибку, заключается в том, что - это возможное столкновение между impl Base для E и impl Base для данных

это оба дженерика, поэтому теоретически я мог бы создать свою собственную структуру и реализовать черту Base<T>, а также черту Ext<T>. Если я это сделаю, ваш код создаст дублирующие реализации для Base<T>::foo, поскольку оба блока impl реализуют Base<T>.

Когда вы удаляете T из своего кода, он становится более конкретным c . Вы бы реализовали Base для E и Base для Data. Вы можете увидеть ту же ошибку во втором примере кода, если добавите следующее сообщение

impl Ext for Data {
    fn bar() {}
}

Это то же самое сообщение basi c. Единственная разница в том, что ваш первый пример предоставляет только вероятность столкновения, тогда как второй (с моим добавлением) фактически вызывает столкновение.

Что касается обходных путей ... черты Rust на самом деле не являются наследование, поэтому я бы сказал, что ржавый способ сделать это - иметь отдельные черты для каждого подмножества функций, которые вы хотите, вместо того, чтобы пытаться создать иерархию черт.

Если вашей программе действительно нужны черты типа наследования, избегайте использования дженериков или привязки их к конкретным типам.

...