¿Qué es un "tipo fundamental" en Rust?

37

En algún lugar tomé el término "tipo fundamental" (y su atributo #[fundamental]) y justo ahora quería aprender más sobre él. Recuerdo vagamente que se trata de relajar las reglas de coherencia en algunas situaciones. Y creo que los tipos de referencia son tipos fundamentales.

Desafortunadamente, buscar en la web no me llevó muy lejos. La referencia de Rust no lo menciona (por lo que puedo ver). Acabo de encontrar un problema sobre cómo hacer tuplas tipos fundamentales y el RFC que introdujo el atributo . Sin embargo, el RFC tiene un solo párrafo sobre tipos fundamentales:

  • Un #[fundamental]tipo Fooes aquel en el que implementar una implicación general Fooes un cambio radical. Según lo descrito, &y &mutson fundamentales. Este atributo se aplicaría a Box, haciendo que se Box comportara igual &y &mutcon respecto a la coherencia.

Encuentro la redacción bastante difícil de entender y siento que necesito un conocimiento profundo del RFC completo para comprender este bit sobre los tipos fundamentales. Esperaba que alguien pudiera explicar los tipos fundamentales en términos algo más simples (sin simplificar demasiado, por supuesto). Esta pregunta también serviría como un conocimiento fácil de encontrar.

Para comprender los tipos fundamentales, me gustaría responder a estas preguntas (además de la pregunta principal "¿qué son?", Por supuesto):

  • ¿Pueden los tipos fundamentales hacer más que los no fundamentales?
  • ¿Puedo, como autor de una biblioteca, beneficiarme de alguna manera al marcar algunos de mis tipos como #[fundamental]?
  • ¿Qué tipos del lenguaje central o biblioteca estándar son fundamentales?
Lukas Kalbertodt
fuente

Respuestas:

34

Normalmente, si una biblioteca tiene un tipo genérico Foo<T>, las cajas posteriores no pueden implementar rasgos en ella, incluso si Tes 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 Timplemente Default), por lo que podemos suponer que no lo hará en el futuro porque Box<T>es fundamental. Tenga en cuenta que implementar Defaultpara Barcausarí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), FnMutestá 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 regexcaja o si se usa en otro lugar.

En teoría, si el Addrasgo 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 .

SCappella
fuente
1
¡Gran respuesta! En cuanto a los tipos primitivos: sólo hay un puñado genérica tipos primitivos: &T, &mut T, *const T, *mut T, [T; N], [T], fnpuntero 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.
Lukas Kalbertodt
1
@LukasKalbertodt Gracias por la información sobre tipos primitivos. He añadido en tus pruebas. En cuanto a la justificación sobre referencias vs punteros, revise este comentario en la solicitud de extracción RFC.
SCappella
La referencia no documenta atributos inestables, por eso no lo encontraste allí.
Havvy