Теперь это затронуто во втором издании языка программирования Rust . Однако давайте немного углубимся в детали.
Начнем с более простого примера.
Когда уместно использовать метод черты?
Существует несколько способов обеспечения позднего связывания :
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>
зависимости от контекста? Тем не менее, автор высказал свое мнение и решил, что перегрузка типа возвращаемого значения для дополнений не требуется.
Лично я считаю, что правильного ответа нет. Тем не менее, помимо аргумента единственности, я хотел бы упомянуть, что связанные типы упрощают использование признака, поскольку они уменьшают количество параметров, которые должны быть указаны, поэтому в случае, если преимущества гибкости использования обычного параметра признака не очевидны, я предлагаю начать со связанного типа.
trait/struct MyTrait/MyStruct
позволяет ровно одноimpl MyTrait for
илиimpl MyStruct
.trait MyTrait<Return>
допускает несколькоimpl
s, потому что он общий.Return
может быть любого типа. Общие структуры такие же.Связанные типы - это механизм группировки , поэтому их следует использовать, когда есть смысл группировать типы вместе.
Graph
Черта введена в документации является примером этого. Вы хотите, чтобы aGraph
был общим, но когда у вас есть конкретный типGraph
, вы не хотите, чтобы типыNode
илиEdge
больше менялись. КонкретныйGraph
объект не захочет изменять эти типы в рамках одной реализации, а фактически хочет, чтобы они всегда были одинаковыми. Они сгруппированы вместе или, можно даже сказать, связаны .источник