Normalmente, si una biblioteca tiene un tipo genérico Foo<T>
, las cajas posteriores no pueden implementar rasgos en ella, incluso si T
es de algún tipo local. Por ejemplo,
( 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)
}
}
Para un ejemplo concreto que funciona en el patio de recreo (es decir, da un error),
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)
}
}
(patio de recreo)
Esto normalmente permite al autor de la caja agregar implementaciones (generales) de rasgos sin romper las cajas posteriores. Eso es genial en los casos en los que inicialmente no es seguro que un tipo implemente un rasgo particular, pero luego queda claro que debería hacerlo. Por ejemplo, podríamos tener algún tipo de tipo numérico que inicialmente no implementa los rasgos num-traits
. Esos rasgos podrían agregarse más tarde sin necesidad de un cambio importante.
Sin embargo, en algunos casos, el autor de la biblioteca quiere que las cajas posteriores puedan implementar los rasgos por sí mismos. Aquí es donde #[fundamental]
entra el atributo. Cuando se coloca en un tipo, cualquier rasgo no implementado actualmente para ese tipo no se implementará (salvo un cambio importante). Como resultado, las cajas aguas abajo pueden implementar rasgos para ese tipo siempre que un parámetro de tipo sea local (existen algunas reglas complicadas para decidir qué parámetros de tipo cuentan para esto). Dado que el tipo fundamental no implementará un rasgo dado, ese rasgo se puede implementar libremente sin causar problemas de coherencia.
Por ejemplo, Box<T>
está marcado #[fundamental]
, por lo que funciona el siguiente código (similar a la Rc<T>
versión anterior). Box<T>
no se implementa Default
(a menos que se T
implemente Default
), por lo que podemos suponer que no lo hará en el futuro porque Box<T>
es fundamental. Tenga en cuenta que implementar Default
para Bar
causaría problemas, ya que entonces Box<Bar>
ya se implementa Default
.
struct Bar;
impl Default for Box<Bar> {
fn default() -> Self {
Box::new(Bar)
}
}
(patio de recreo)
Por otro lado, los rasgos también se pueden marcar con #[fundamental]
. Esto tiene un doble significado para los tipos fundamentales. Si algún tipo no implementa actualmente un rasgo fundamental, se puede suponer que ese tipo no lo implementará en el futuro (nuevamente, salvo un cambio importante). No estoy exactamente seguro de cómo se usa esto en la práctica. En el código (vinculado a continuación), FnMut
está marcado fundamental con la nota de que es necesario para la expresión regular (algo sobre &str: !FnMut
). No pude encontrar dónde se usa en la regex
caja o si se usa en otro lugar.
En teoría, si el Add
rasgo se marcara como fundamental (lo cual se ha discutido), esto podría usarse para implementar la suma entre cosas que aún no lo tienen. Por ejemplo, agregar [MyNumericType; 3]
(pointwise), que podría ser útil en ciertas situaciones (por supuesto, hacer [T; N]
fundamental también lo permitiría).
Los tipos fundamentales primitivos son &T
, &mut T
(vea aquí una demostración de todos los tipos primitivos genéricos). En la biblioteca estándar, Box<T>
y Pin<T>
también están marcados como fundamentales.
Los rasgos fundamentales en la biblioteca estándar son Sized
, Fn<T>
, FnMut<T>
, FnOnce<T>
y Generator
.
Tenga en cuenta que el #[fundamental]
atributo es actualmente inestable. El problema de seguimiento es el problema # 29635 .
&T
,&mut T
,*const T
,*mut T
,[T; N]
,[T]
,fn
puntero y tuplas. Y probándolos a todos (por favor dígame si este código no tiene sentido) parece que las referencias son los únicos tipos primitivos fundamentales . Interesante. Me interesaría saber el razonamiento por el que los demás no lo son, especialmente los punteros en bruto. Pero ese no es el alcance de esta pregunta, supongo.