Подтвердить что ты не робот

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

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

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

В этом RFC разъясняются особенности соответствия:

  • Обрабатывать все параметры типа черты как типы ввода и
  • Предоставление связанных типов, которые являются типами вывода.

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

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

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

4b9b3361

Ответ 1

Это теперь затронуто во втором издании The Rust Programming Language. Тем не менее, давайте немного углубимся.

Давайте начнем с более простого примера.

Когда уместно использовать метод черты?

Существует несколько способов обеспечить позднюю привязку:

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 позволяет иметь другое поведение на за основу.

Уместно ли использовать метод, зависит от варианта использования!

Когда уместно использовать связанный тип?

Подобно методам 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 является очевидным вариантом использования (техническое ограничение), случай Add менее ясен: возможно, было бы целесообразно, чтобы i32 + i32 давал либо i32, либо Complex<i32> в зависимости от контекст? Тем не менее автор использовал свое суждение и решил, что перегрузка возвращаемого типа для дополнений не нужна.

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

Ответ 2

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

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

Ответ 3

Я прихожу из мира Swift/iOS, но я думаю, что эти понятия применимы:

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

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