Коли доцільно використовувати асоційований тип проти загального типу?


108

У цьому питанні виникла проблема, яку можна було б вирішити, змінивши спробу використання параметра загального типу на асоційований тип. Це підштовхнуло питання "Чому асоційований тип тут більше підходить?", Який змусив мене знати більше.

RFC , які введені пов'язані типи каже:

Цей RFC роз'яснює відповідність ознак за допомогою:

  • Трактування всіх параметрів типу ознаки як типів вводу та
  • Надання асоційованих типів, які є вихідними типами .

RFC використовує структуру графіків як мотивуючий приклад, і це також використовується в документації , але я визнаю, що не повністю оцінював переваги асоційованої версії типу над версією, що параметризує тип. Основне в тому, що distanceметоду не потрібно дбати про Edgeтип. Це приємно, але здається трохи неглибокою причиною наявності асоційованих типів взагалі.

Я виявив, що асоційовані типи є досить інтуїтивно зрозумілими для використання на практиці, але я намагаюся вирішувати, де і коли я повинен їх використовувати у власному API.

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

Відповіді:


75

Зараз це торкнулося у другому випуску Мова програмування "Іржа" . Однак давайте зануримось трохи на додачу.

Почнемо з більш простого прикладу.

Коли доцільно використовувати метод ознаки?

Існує кілька способів забезпечити пізнє зв'язування :

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, я хотів би зазначити, що асоційовані типи полегшують використання ознаки, оскільки вони зменшують кількість параметрів, які необхідно вказати, тому у випадку, якщо переваги гнучкості використання звичайного параметра ознаки не очевидні, я запропонуйте почати з асоційованого типу.


4
Дозвольте спробувати трохи спростити: trait/struct MyTrait/MyStructдозволяє рівно один impl MyTrait forабо impl MyStruct. trait MyTrait<Return>дозволяє кілька impls, тому що це загальне. Returnможе бути будь-якого типу. Родові структури однакові.
Пол-Себастьян Маноле

2
Я вважаю вашу відповідь набагато простішою для розуміння, ніж відповідь у "Мові програмування іржі"
drojf

"перший встановлює, що для даного" Я "є єдине Повернення, пов'язане". Це вірно в прямому сенсі, але можна, звичайно, обійти це обмеження, підкласируючи загальну ознаку. Можливо, єдиність може бути лише навіюванням, а не примусовою
joel

36

Асоційовані типи є механізмом групування , тому їх слід використовувати, коли є сенс групувати типи разом.

GraphРиса введена в документації є прикладом цього. Ви хочете Graphбути родовими, але, маючи певний вид Graph, ви більше не хочете, щоб типи Nodeабо Edgeтипи змінювалися. Зокрема Graph, не хоче змінювати ці типи в межах однієї реалізації, а насправді хоче, щоб вони завжди були однаковими. Вони згруповані разом, або можна сказати навіть асоційовані .


4
Щоб зрозуміти, мені знадобилося трохи часу. Для мене це більше схоже на визначення декількох типів одразу: Edge та Node не мають сенсу з графіка.
tafia
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.