web-dev-qa-db-fra.com

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

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 mené très loin. La référence Rust référence ne la mentionne pas (pour autant que je puisse voir). Je viens de trouver n problème de 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:

  • UNE #[fundamental] type Foo est celui où l'implémentation d'une implémentation de couverture sur Foo est un changement décisif. Comme décrit, & et &mut sont fondamentaux. Cet attribut serait appliqué à Box, faisant que Box se comporterait de la même manière que & et &mut en ce qui 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 serait également un élément 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 de marquer certains de mes types comme #[fundamental]?
  • Quels types du langage principal ou de la bibliothèque standard sont fondamentaux?
37
Lukas Kalbertodt

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 si T est 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)
    }
}

(aire 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 génial 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 n'implémente pas 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 puissent implémenter elles-mêmes les traits. C'est là que l'attribut #[fundamental] 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 mis en œuvre librement sans causer de problèmes de cohérence.

Par exemple, Box<T> Est marqué #[fundamental], Donc le code suivant (similaire à la version Rc<T> Ci-dessus) fonctionne. Box<T> N'implémente pas Default (sauf si T implémente Default), nous pouvons donc supposer que ce ne sera pas le cas à l'avenir parce que Box<T> est fondamentale. Notez que l'implémentation de Default pour Bar entraînerait des problèmes, car Box<Bar> Implémente déjà Default.

struct Bar;

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

(aire de jeux)


D'autre part, 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), FnMut est marqué comme fondamental avec la note qu'il est nécessaire pour l'expression régulière (quelque chose à propos de &str: !FnMut). Je n'ai pas pu trouver où il est utilisé dans la caisse regex ou s'il est utilisé ailleurs.

En théorie, si le trait Add é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 de [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'attribut #[fundamental] Est actuellement instable. Le problème de suivi est problème # 29635 .

34
SCappella