Когда целесообразно использовать связанный тип по сравнению с общим типом?


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

The RFC, который ввел связанные типы говорит:

в этом документе уточняется соответствие признака владельца:

  • обработка всех параметров типа признака как типы входов и
  • предоставление связанных типов, которые вывод:.

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

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

при написании кода, когда я должен выбрать соответствующий тип параметра универсального типа, и когда я должен сделать наоборот?

3 60

3 ответа:

теперь это описано в второе издание Язык Программирования Ржавчины. Однако давайте немного погрузимся в дополнение.

давайте начнем с простого примера.

когда целесообразно использовать метод черта?

есть несколько способов, чтобы обеспечить позднее связывание:

trait MyTrait {
    fn hello_word(&self) -> String;
}

или:

struct MyTrait<T> {
    t: T,
    hello_world: fn(&T) -> String,
}

impl<T> MyTrait<T> {
    fn new(t: T, hello_world: fn(&T) -> String) -> MyTrait<T>;

    fn hello_world(&self) -> String {
        (self.hello_world)(self.t)
    }
}

игнорируя любые реализация / стратегия производительности, оба отрывка выше позволяют пользователю указать в динамическом порядке, как hello_world должны вести себя.

единственное различие (семантически) заключается в том, что trait реализация гарантирует, что для данного типа T реализация trait,hello_world всегда будет иметь такое же поведение, тогда как struct реализация позволяет иметь различное поведение на основе экземпляра.

подходит ли использование метода или нет зависит от usecase!

когда целесообразно использовать связанный тип?

аналогично trait методы выше, связанный тип является формой позднего связывания (хотя это происходит при компиляции), что позволяет пользователю trait указать для данного экземпляра, какой тип для замены. Это не единственный способ (Таким образом, вопрос):

trait MyTrait {
    type Return;
    fn hello_world(&self) -> Self::Return;
}

или:

trait MyTrait<Return> {
    fn hello_world(&Self) -> Return;
}

эквивалентны позднему связыванию методов выше:

  • первый обеспечивает это для данного Self есть один Return связан
  • второй, вместо этого, позволяет реализовать MyTrait на Self за несколько Return

какая форма является более подходящей, зависит от того, имеет ли смысл применять единство или нет. Например:

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

Как видите, пока Deref является очевидным usecase (техническое ограничение), случай Add менее ясно: может быть, это имело бы смысл для i32 + i32 выход либо i32 или Complex<i32> в зависимости от контекста? Тем не менее автор осуществил свое решение и постановил, что перегрузка возвращения тип для дополнений был излишним.

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

связанные типы-это группировка механизм, поэтому они должны использоваться, когда имеет смысл группировать типы вместе.

The Graph черта, представленная в документации, является примером этого. Вы хотите Graph чтобы быть общим, но как только у вас есть определенный вид Graph, вы не хотите Node или Edge типы не различаются. А конкретно Graph не собирается менять эти типы в рамках одной реализации, и на самом деле, хочет, чтобы они всегда быть одним и тем же. Они сгруппированы вместе, или можно даже сказать связан.

Я иду из мира Swift / iOS, но я думаю, что концепции применяются:

  • на одиночная функция вы бы использовали универсальные выражения для ограничения нескольких параметров функции.

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