Normalmente, se uma biblioteca tiver um tipo genérico Foo<T>
, caixas a jusante não poderão implementar características nela, mesmo que T
seja algum tipo local. Por exemplo,
( 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 um exemplo concreto que funciona no playground (ou seja, dá um erro),
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)
}
}
(Parque infantil)
Isso normalmente permite que o autor da caixa adicione implementações (gerais) de características sem quebrar caixas a jusante. Isso é ótimo nos casos em que não é inicialmente certo que um tipo deve implementar uma característica específica, mas depois fica claro que deveria. Por exemplo, podemos ter algum tipo de tipo numérico que inicialmente não implementa os traços num-traits
. Essas características podem ser adicionadas mais tarde, sem a necessidade de uma alteração de última hora.
No entanto, em alguns casos, o autor da biblioteca deseja que as caixas a jusante possam implementar as próprias características. É aqui que o #[fundamental]
atributo entra. Quando colocado em um tipo, qualquer característica não implementada no momento para esse tipo não será implementada (exceto uma mudança de quebra). Como resultado, caixas a jusante podem implementar características para esse tipo, desde que um parâmetro de tipo seja local (existem algumas regras complicadas para decidir quais parâmetros de tipo contam para isso). Como o tipo fundamental não implementa uma determinada característica, essa característica pode ser implementada livremente sem causar problemas de coerência.
Por exemplo, Box<T>
está marcado #[fundamental]
, portanto, o código a seguir (semelhante à Rc<T>
versão acima) funciona. Box<T>
não implementa Default
(a menos que T
implemente Default
) para que possamos assumir que não será implementado no futuro porque Box<T>
é fundamental. Observe que implementar Default
para Bar
causaria problemas, desde então Box<Bar>
já implementa Default
.
struct Bar;
impl Default for Box<Bar> {
fn default() -> Self {
Box::new(Bar)
}
}
(Parque infantil)
Por outro lado, os traços também podem ser marcados com #[fundamental]
. Isso tem um significado duplo para os tipos fundamentais. Se algum tipo não implementar atualmente uma característica fundamental, pode-se presumir que esse tipo não a implementará no futuro (novamente, exceto em uma mudança de última hora). Não sei exatamente como isso é usado na prática. No código (link abaixo), FnMut
está marcado como fundamental com a observação de que é necessário para o regex (algo sobre &str: !FnMut
). Não consegui encontrar onde é usado no regex
caixote ou se é usado em outro lugar.
Em teoria, se a Add
característica fosse marcada como fundamental (o que foi discutido), isso poderia ser usado para implementar a adição entre coisas que ainda não a possuem. Por exemplo, adicionando [MyNumericType; 3]
(pointwise), o que poderia ser útil em determinadas situações (é claro, tornar [T; N]
fundamental também permitiria isso).
Os tipos fundamentais primitivas são &T
, &mut T
(ver aqui para uma demonstração de todos os tipos primitivos genéricos). Na biblioteca padrão, Box<T>
e Pin<T>
também são marcados como fundamentais.
Os traços fundamentais da biblioteca padrão são Sized
, Fn<T>
, FnMut<T>
, FnOnce<T>
e Generator
.
Observe que o #[fundamental]
atributo está instável no momento. O problema de rastreamento é o número 29635 .
&T
,&mut T
,*const T
,*mut T
,[T; N]
,[T]
,fn
ponteiro e tuplas. E testando todos eles (por favor, diga-me se esse código não faz sentido) , parece que as referências são os únicos tipos primitivos fundamentais . Interessante. Eu estaria interessado em saber o raciocínio por que os outros não são, especialmente indicadores brutos. Mas esse não é o escopo desta pergunta, eu acho.