O que é um "tipo fundamental" em Rust?

37

Em algum lugar, peguei o termo "tipo fundamental" (e seu atributo #[fundamental]) e agora queria aprender mais sobre ele. Lembro-me vagamente de relaxar as regras de coerência em algumas situações. E acho que os tipos de referência são tipos fundamentais.

Infelizmente, pesquisar na web não me levou muito longe. A referência à Rust não a menciona (tanto quanto posso ver). Acabei de encontrar um problema ao criar tipos fundamentais de tuplas e a RFC que introduziu o atributo . No entanto, a RFC possui um único parágrafo sobre tipos fundamentais:

  • Um #[fundamental]tipo Fooé aquele em que implementar uma implementação geral Fooé uma mudança radical. Conforme descrito, &e &mutsão fundamentais. Esse atributo seria aplicado Box, fazendo com que Box se comportasse da mesma forma &e &mutcom relação à coerência.

Acho as palavras bastante difíceis de entender e parece que preciso de um conhecimento profundo da RFC completa para entender esse pouco sobre os tipos fundamentais. Eu esperava que alguém pudesse explicar tipos fundamentais em termos um pouco mais simples (sem simplificar muito, é claro). Essa pergunta também serviria como um conhecimento fácil de encontrar.

Para entender os tipos fundamentais, eu gostaria de responder a estas perguntas (além da pergunta principal "o que são iguais?", É claro):

  • Os tipos fundamentais podem fazer mais do que os não fundamentais?
  • Como autor de uma biblioteca, posso me beneficiar de alguma forma ao marcar alguns dos meus tipos como #[fundamental]?
  • Quais tipos da linguagem principal ou da biblioteca padrão são fundamentais?
Lukas Kalbertodt
fonte

Respostas:

34

Normalmente, se uma biblioteca tiver um tipo genérico Foo<T>, caixas a jusante não poderão implementar características nela, mesmo que Tseja 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 Timplemente Default) para que possamos assumir que não será implementado no futuro porque Box<T>é fundamental. Observe que implementar Defaultpara Barcausaria 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), FnMutestá 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 regexcaixote ou se é usado em outro lugar.

Em teoria, se a Addcaracterí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 .

SCappella
fonte
11
Ótima resposta! Em relação a tipos primitivos: há apenas um punhado genérico tipos primitivos: &T, &mut T, *const T, *mut T, [T; N], [T], fnponteiro 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.
Lukas Kalbertodt 25/11/19
11
@LukasKalbertodt Obrigado pela informação sobre os tipos primitivos. Eu adicionei nos seus testes. Quanto à justificativa sobre referências versus ponteiros, verifique este comentário na solicitação de recebimento da RFC.
SCappella
A referência não documenta atributos instáveis, por isso você não a encontrou lá.
Havvy 29/11/19