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