Zwykle, jeśli biblioteka ma typ ogólny Foo<T>
, dalsze skrzynki nie mogą zaimplementować na niej cech, nawet jeśliT
jest to typ lokalny. Na przykład,
( crate_a
)
struct Foo<T>(pub t: T)
( crate_b
)
use crate_a::Foo;
struct Bar;
// This causes an error
impl Clone for Foo<Bar> {
fn clone(&self) -> Self {
Foo(Bar)
}
}
Na konkretny przykład, który działa na placu zabaw (czyli daje błąd),
use std::rc::Rc;
struct Bar;
// This causes an error
// error[E0117]: only traits defined in the current crate
// can be implemented for arbitrary types
impl Default for Rc<Bar> {
fn default() -> Self {
Rc::new(Bar)
}
}
(plac zabaw)
Zwykle umożliwia to autorowi skrzynek dodawanie (ogólnych) implementacji cech bez rozbijania dalszych skrzyń. Jest to świetne w przypadkach, gdy początkowo nie jest pewne, czy typ powinien implementować określoną cechę, ale później staje się jasne, że powinien. Na przykład, możemy mieć jakiś typ liczbowy, który początkowo nie implementuje cech z num-traits
. Te cechy mogą być dodane później, bez potrzeby przełomowej zmiany.
Jednak w niektórych przypadkach autor biblioteki chce, aby skrzynie niższego rzędu mogły same implementować cechy. To gdzie#[fundamental]
pojawia się atrybut. Po umieszczeniu na typie, żadna cecha, która nie jest obecnie zaimplementowana dla tego typu, nie zostanie zaimplementowana (z wyjątkiem zmiany przełomowej). W rezultacie skrzynki odbiorcze mogą implementować cechy tego typu, o ile parametr typu ma charakter lokalny (istnieją pewne skomplikowane zasady decydowania o tym, które parametry typu się liczą). Ponieważ typ podstawowy nie wdroży danej cechy, cechę tę można swobodnie wdrożyć bez powodowania problemów ze spójnością.
Na przykład Box<T>
jest zaznaczony #[fundamental]
, więc działa następujący kod (podobny do Rc<T>
powyższej wersji). Box<T>
nie implementuje Default
(chyba że T
implementuje Default
), więc możemy założyć, że nie będzie to w przyszłości, ponieważ Box<T>
jest fundamentalne. Zauważ, że implementacja Default
dla Bar
spowodowałaby problemy, ponieważ Box<Bar>
już wtedy implementuje Default
.
struct Bar;
impl Default for Box<Bar> {
fn default() -> Self {
Box::new(Bar)
}
}
(plac zabaw)
Z drugiej strony cechy mogą być również oznaczone #[fundamental]
. Ma to podwójne znaczenie dla typów podstawowych. Jeśli jakikolwiek typ nie implementuje obecnie fundamentalnej cechy, można założyć, że ten typ nie wdroży jej w przyszłości (ponownie, z wyjątkiem przełomowej zmiany). Nie jestem do końca pewien, jak to się stosuje w praktyce. W kodzie (link poniżej) FnMut
jest zaznaczony jako podstawa z uwagą, że jest potrzebny do wyrażenia regularnego (coś o &str: !FnMut
). Nie mogłem znaleźć, gdzie jest używany w regex
skrzyni lub czy jest używany gdzie indziej.
Teoretycznie, gdyby Add
cechę oznaczono jako fundamentalną (co zostało omówione), można by ją zastosować do implementacji dodawania między rzeczami, które jeszcze jej nie mają. Na przykład dodawanie [MyNumericType; 3]
(punktowe), które może być przydatne w niektórych sytuacjach (oczywiście, uczynienie [T; N]
podstawowymi również by na to pozwoliło).
Prymitywne podstawowe typy &T
, &mut T
(patrz tutaj dla demonstracji wszystkich ogólnych prymitywnych typów). W standardowej bibliotece, Box<T>
a Pin<T>
także oznaczone jako fundamentalne.
Podstawowe cechy w standardowej biblioteki są Sized
, Fn<T>
, FnMut<T>
, FnOnce<T>
i Generator
.
Pamiętaj, że ten #[fundamental]
atrybut jest obecnie niestabilny. Problem ze śledzeniem to numer 29635 .
&T
,&mut T
,*const T
,*mut T
,[T; N]
,[T]
,fn
wskaźnik i krotki. A testowanie ich wszystkich (proszę powiedz mi, jeśli ten kod nie ma sensu) wydaje się, że referencje są jedynymi podstawowymi typami pierwotnymi . Ciekawy. Byłbym zainteresowany poznaniem powodów, dla których inni nie są, szczególnie surowych wskazówek. Ale chyba nie jest to zakres tego pytania.