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

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

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

Этот RFC уточняет соответствие признаков:

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

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

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

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

Это описано во втором издании «Язык программирования ржавчины» . Однако, давайте немного поплавать.

Начнем с более простого примера.

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

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

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

Или:

 struct MyTrait { t: T, hello_world: fn(&T) -> String, } impl MyTrait { fn new(t: T, hello_world: fn(&T) -> String) -> MyTrait; fn hello_world(&self) -> String { (self.hello_world)(self.t) } } 

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

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

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

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

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

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

Или:

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

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

  • первый из них предусматривает, что для данного « Self существует одно связанное с Return
  • вместо этого второй позволяет реализовать MyTrait для Self для множественного Return

Какая форма более уместна, зависит от того, имеет ли смысл применять унификацию или нет. Например:

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

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

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

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

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

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

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

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

  • Почему некоторые люди предпочитают «T const &» над «const T &»?
  • Есть ли способ ссылаться на текущий тип с переменной типа?
  • Тип указателя возвращаемой функции
  • Как найти числовые столбцы в Pandas?
  • JAXB: Как изменить имена classов, сгенерированных XJC, когда тип attr указан в XSD?
  • Типы данных PostgreSQL и C #
  • Что такое типы POD в C ++?
  • Должен ли я использовать #include рядом с ?
  • Почему я могу вводить функции псевдонима и использовать их без кастинга?
  • Какой тип данных использовать для hashированного поля пароля и какой длины?
  • Как определить разные типы для одного и того же classа в C ++
  • Давайте будем гением компьютера.