Qu'est-ce qu'un «type fondamental» dans Rust?

37

Quelque part, j'ai choisi le terme "type fondamental" (et son attribut #[fundamental]) et je voulais en savoir plus à ce sujet. Je me souviens vaguement qu'il s'agissait d'assouplir les règles de cohérence dans certaines situations. Et je pense que les types de référence sont des types fondamentaux.

Malheureusement, la recherche sur le Web ne m'a pas amené très loin. La référence Rust ne le mentionne pas (pour autant que je puisse voir). Je viens de découvrir un problème concernant la création de types fondamentaux de tuples et le RFC qui a introduit l'attribut . Cependant, la RFC a un seul paragraphe sur les types fondamentaux:

  • Un #[fundamental]type Fooest celui où l'implémentation d'une implémentation de couverture Fooest un changement de rupture. Comme décrit, &et &mutsont fondamentaux. Cet attribut serait appliqué à Box, faisant Box se comporter la même chose &et en ce &mutqui concerne la cohérence.

Je trouve le libellé assez difficile à comprendre et j'ai l'impression d'avoir besoin d'une connaissance approfondie de la RFC complète pour comprendre ce bit sur les types fondamentaux. J'espérais que quelqu'un pourrait expliquer les types fondamentaux en termes un peu plus simples (sans trop simplifier, bien sûr). Cette question servirait également de connaissance facile à trouver.

Pour comprendre les types fondamentaux, je voudrais répondre à ces questions (en plus de la question principale "qu'est-ce qu'ils sont même?", Bien sûr):

  • Les types fondamentaux peuvent-ils faire plus que les types non fondamentaux?
  • Puis-je, en tant qu'auteur de bibliothèque, bénéficier d'une manière ou d'une autre du marquage de certains de mes types #[fundamental]?
  • Quels types du langage principal ou de la bibliothèque standard sont fondamentaux?
Lukas Kalbertodt
la source

Réponses:

34

Normalement, si une bibliothèque a un type générique Foo<T>, les caisses en aval ne peuvent pas implémenter de traits dessus, même s'il Ts'agit d'un type local. Par exemple,

( 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)
    }
}

Pour un exemple concret qui fonctionne sur le terrain de jeu (c'est-à-dire, donne une erreur),

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)
    }
}

(terrain de jeux)


Cela permet normalement à l'auteur de la caisse d'ajouter des mises en œuvre (générales) de traits sans casser les caisses en aval. C'est formidable dans les cas où il n'est pas initialement certain qu'un type doit implémenter un trait particulier, mais il devient plus tard clair que c'est le cas. Par exemple, nous pourrions avoir une sorte de type numérique qui initialement ne met pas en œuvre les traits de num-traits. Ces traits pourraient être ajoutés plus tard sans avoir besoin d'un changement de rupture.

Cependant, dans certains cas, l'auteur de la bibliothèque souhaite que les caisses en aval soient capables d'implémenter les traits elles-mêmes. C'est là que l' #[fundamental]attribut entre en jeu. Lorsqu'il est placé sur un type, tout trait non implémenté actuellement pour ce type ne sera pas implémenté (sauf un changement de rupture). Par conséquent, les caisses en aval peuvent implémenter des traits pour ce type tant qu'un paramètre de type est local (il existe des règles compliquées pour décider quels paramètres de type comptent pour cela). Étant donné que le type fondamental n'implémentera pas un trait donné, ce trait peut être librement mis en œuvre sans causer de problèmes de cohérence.

Par exemple, Box<T>est marqué #[fundamental], donc le code suivant (similaire à la Rc<T>version ci-dessus) fonctionne. Box<T>n'implémente pas Default(sauf s'il Timplémente Default) donc nous pouvons supposer que ce ne sera pas le cas à l'avenir car Box<T>c'est fondamental. Notez que l'implémentation de Defaultfor Barentraînerait des problèmes, car Box<Bar>déjà implémente Default.

struct Bar;

impl Default for Box<Bar> {
    fn default() -> Self {
        Box::new(Bar)
    }
}

(terrain de jeux)


D'un autre côté, les traits peuvent également être marqués avec #[fundamental]. Cela a une double signification pour les types fondamentaux. Si un type n'implémente pas actuellement un trait fondamental, on peut supposer que ce type ne l'implémentera pas à l'avenir (encore une fois, à moins d'un changement de rupture). Je ne sais pas exactement comment cela est utilisé dans la pratique. Dans le code (lié ci-dessous), FnMutest marqué fondamental avec la note qu'il est nécessaire pour l'expression régulière (quelque chose à propos &str: !FnMut). Je n'ai pas pu trouver où il est utilisé dans la regexcaisse ou s'il est utilisé ailleurs.

En théorie, si le Addtrait était marqué comme fondamental (ce qui a été discuté), cela pourrait être utilisé pour implémenter l'addition entre des choses qui ne l'ont pas déjà. Par exemple, l'ajout [MyNumericType; 3](point par point), qui pourrait être utile dans certaines situations (bien sûr, rendre [T; N]fondamental permettrait également cela).


Les types fondamentaux primitifs sont &T, &mut T(voir ici pour une démonstration de tous les types primitifs génériques). Dans la bibliothèque standard, Box<T>et Pin<T>sont également marqués comme fondamentaux.

Les traits fondamentaux de la bibliothèque standard sont Sized, Fn<T>, FnMut<T>, FnOnce<T>et Generator.


Notez que l' #[fundamental]attribut est actuellement instable. Le problème de suivi est le numéro # 29635 .

SCappella
la source
1
Très bonne réponse! En ce qui concerne les types primitifs: il y a seulement une poignée générique types primitifs: &T, &mut T, *const T, *mut T, [T; N], [T], fnpointeur et tuples. Et en les testant tous (dites-moi si ce code n'a pas de sens), il semble que les références soient les seuls types primitifs fondamentaux . Intéressant. Je serais intéressé de savoir pourquoi les autres ne le sont pas, en particulier les pointeurs bruts. Mais ce n'est pas la portée de cette question, je suppose.
Lukas Kalbertodt
1
@LukasKalbertodt Merci pour les informations sur les types primitifs. J'ai ajouté dans vos tests. En ce qui concerne la justification des références par rapport aux pointeurs, consultez ce commentaire dans la demande d'extraction RFC.
SCappella
La référence ne documente pas les attributs instables, c'est pourquoi vous ne l'avez pas trouvée ici.
Havvy