Jest to teraz poruszane w drugiej edycji Języka programowania Rusta . Jednak zanurzmy się trochę dodatkowo.
Zacznijmy od prostszego przykładu.
Kiedy należy stosować metodę cech?
Istnieje wiele sposobów zapewnienia późnego wiązania :
trait MyTrait {
fn hello_word(&self) -> String;
}
Lub:
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)
}
}
Pomijając jakąkolwiek strategię wdrożenia / wydajności, oba powyższe fragmenty pozwalają użytkownikowi dynamicznie określić, jak hello_world
powinien się zachować.
Jedyną różnicą (semantycznie) jest to, że trait
implementacja gwarantuje, że dla danego typu T
implementacja trait
, hello_world
zawsze będzie miała takie samo zachowanie, podczas gdy struct
implementacja pozwala na inne zachowanie na podstawie instancji.
To, czy użycie metody jest właściwe, czy nie, zależy od przypadku użycia!
Kiedy należy użyć powiązanego typu?
Podobnie jak w przypadku trait
powyższych metod, skojarzony typ jest formą późnego wiązania (chociaż występuje podczas kompilacji), umożliwiając użytkownikowi trait
określenie dla danej instancji, który typ ma zastąpić. To nie jedyny sposób (stąd pytanie):
trait MyTrait {
type Return;
fn hello_world(&self) -> Self::Return;
}
Lub:
trait MyTrait<Return> {
fn hello_world(&Self) -> Return;
}
Są równoważne z późnym wiązaniem powyższych metod:
- pierwszy wymusza, że dla danego
Self
jest jeden Return
powiązany
- druga natomiast pozwala na zaimplementowanie
MyTrait
do Self
do wielokrotnościReturn
To, która forma jest bardziej odpowiednia, zależy od tego, czy wymuszanie jedności ma sens, czy nie. Na przykład:
Deref
używa skojarzonego typu, ponieważ bez unikalności kompilator zwariowałby podczas wnioskowania
Add
używa skojarzonego typu, ponieważ jego autor uważał, że biorąc pod uwagę te dwa argumenty, będzie to logiczny typ zwracany
Jak widać, gdy Deref
jest oczywiste, USECASE (ograniczenie techniczny), sprawa Add
jest mniej jednoznaczne: może to mieć sens dla i32 + i32
uzyskując albo i32
czy Complex<i32>
w zależności od kontekstu? Niemniej jednak autor wywiązał się ze swojego osądu i uznał, że przeładowanie zwracanego typu dla dodatków nie jest konieczne.
Osobiście uważam, że nie ma właściwej odpowiedzi. Mimo wszystko, poza argumentem unicity, wspomniałbym, że powiązane typy ułatwiają korzystanie z cechy, ponieważ zmniejszają liczbę parametrów, które należy określić, więc w przypadku, gdy korzyści płynące z elastyczności stosowania zwykłego parametru cechy nie są oczywiste, ja sugeruj rozpoczęcie od powiązanego typu.
trait/struct MyTrait/MyStruct
pozwala dokładnie na jedenimpl MyTrait for
lubimpl MyStruct
.trait MyTrait<Return>
zezwala na wieleimpl
s, ponieważ jest ogólny.Return
może być dowolnego typu. Struktury ogólne są takie same.Powiązane typy są mechanizmem grupującym , dlatego należy ich używać, gdy ma sens grupowanie typów razem.
Graph
Cecha wprowadzone w dokumentacji jest tego przykładem. Chcesz,Graph
aby był ogólny, ale gdy masz już określony rodzajGraph
, nie chcesz już, aby typyNode
lubEdge
się zmieniały. KonkretnyGraph
nie będzie chciał zmieniać tych typów w ramach jednej implementacji, a właściwie chce, aby zawsze były takie same. Są zgrupowane razem, a nawet można powiedzieć, że są powiązane .źródło