Теперь это затронуто во втором издании языка программирования 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>допускает несколькоimpls, потому что он общий.Returnможет быть любого типа. Общие структуры такие же.