Che cos'è un "tipo fondamentale" in Rust?


37

Da qualche parte ho preso il termine "tipo fondamentale" (e il suo attributo #[fundamental]) e proprio ora volevo saperne di più. Ricordo vagamente che si trattava di rilassare le regole di coerenza in alcune situazioni. E penso che i tipi di riferimento siano tali tipi fondamentali.

Sfortunatamente, la ricerca sul web non mi ha portato molto lontano. Il riferimento Rust non lo menziona (per quanto posso vedere). Ho appena riscontrato un problema relativo alla creazione di tipi fondamentali di tuple e alla RFC che ha introdotto l'attributo . Tuttavia, la RFC ha un singolo paragrafo sui tipi fondamentali:

  • Un #[fundamental]tipo Fooè quello in cui l'implementazione di una coperta sopra Fooè un cambiamento di rottura. Come descritto, &e &mutsono fondamentali. Questo attributo verrebbe applicato Box, facendo Box comportare lo stesso &e &mutrispetto alla coerenza.

Trovo la formulazione abbastanza difficile da capire e mi sembra di aver bisogno di una conoscenza approfondita dell'intero RFC per capire questo aspetto dei tipi fondamentali. Speravo che qualcuno potesse spiegare i tipi fondamentali in termini un po 'più semplici (senza semplificare troppo, ovviamente). Questa domanda servirebbe anche da conoscenza facilmente reperibile.

Per comprendere i tipi fondamentali, mi piacerebbe rispondere a queste domande (oltre alla domanda principale "che cosa sono?", Ovviamente):

  • I tipi fondamentali possono fare più di quelli non fondamentali?
  • Come autore della biblioteca, posso beneficiare in qualche modo di contrassegnare alcuni dei miei tipi come #[fundamental]?
  • Quali tipi dalla lingua principale o dalla libreria standard sono fondamentali?

Risposte:


34

Normalmente, se una libreria ha un tipo generico Foo<T>, le cassette a valle non possono implementare i tratti su di essa, anche se Tè un tipo locale. Per esempio,

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

Per un esempio concreto che funziona nel parco giochi (ovvero fornisce un errore),

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

(terreno di gioco)


Ciò normalmente consente all'autore della cassa di aggiungere (coperta) implementazioni di tratti senza rompere le cassette a valle. È fantastico nei casi in cui inizialmente non è certo che un tipo debba implementare un tratto particolare, ma in seguito diventa chiaro che dovrebbe. Ad esempio, potremmo avere una sorta di tipo numerico che inizialmente non implementa i tratti num-traits. Quei tratti potrebbero essere aggiunti in un secondo momento senza bisogno di un cambiamento di rottura.

Tuttavia, in alcuni casi, l'autore della biblioteca desidera che le cassette a valle siano in grado di implementare i tratti da soli. È qui che #[fundamental]entra in gioco l' attributo. Se posizionato su un tipo, qualsiasi tratto non attualmente implementato per quel tipo non verrà implementato (salvo una modifica di rottura). Di conseguenza, le casse a valle possono implementare tratti per quel tipo purché un parametro di tipo sia locale (ci sono alcune regole complicate per decidere quali parametri di tipo contano per questo). Poiché il tipo fondamentale non implementa un determinato tratto, quel tratto può essere implementato liberamente senza causare problemi di coerenza.

Ad esempio, Box<T>è contrassegnato #[fundamental], quindi il seguente codice (simile alla Rc<T>versione precedente) funziona. Box<T>non implementa Default(a meno che non Timplementi Default), quindi possiamo presumere che non lo farà in futuro perché Box<T>è fondamentale. Si noti che l'implementazione Defaultper Barcauserebbe problemi, da allora Box<Bar>già implementa Default.

struct Bar;

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

(terreno di gioco)


D'altra parte, i tratti possono anche essere contrassegnati con #[fundamental] . Questo ha un duplice significato per i tipi fondamentali. Se un tipo attualmente non implementa un tratto fondamentale, si può presumere che quel tipo non lo implementerà in futuro (di nuovo, salvo un cambiamento di rottura). Non sono esattamente sicuro di come questo sia usato in pratica. Nel codice (linkato di seguito), FnMutè contrassegnato fondamentale con la nota che è necessario per regex (qualcosa su &str: !FnMut). Non riuscivo a trovare dove fosse usato nella regexcassa o se fosse usato altrove.

In teoria, se il Add tratto fosse marcato fondamentale (che è stato discusso), questo potrebbe essere usato per implementare l'addizione tra cose che non lo hanno già. Ad esempio, l'aggiunta [MyNumericType; 3](in senso puntuale), che potrebbe essere utile in determinate situazioni (ovviamente, rendere [T; N]fondamentale consentirebbe anche questo).


I tipi fondamentali primitivi sono &T, &mut T(vedi qui per una dimostrazione di tutti i tipi primitivi generici). Nella libreria standard,Box<T> e Pin<T>sono anche contrassegnati come fondamentali.

I tratti fondamentali della libreria standard sono Sized, Fn<T>, FnMut<T>, FnOnce<T>eGenerator .


Si noti che l' #[fundamental]attributo è attualmente instabile. Il problema di tracciamento è il numero 29635 .


1
Bella risposta! Per quanto riguarda i tipi primitivi: ci sono solo una manciata generica tipi primitivi: &T, &mut T, *const T, *mut T, [T; N], [T], fnpuntatore e tuple. E testandoli tutti (per favore dimmi se questo codice non ha senso) sembra che i riferimenti siano gli unici tipi primitivi fondamentali . Interessante. Sarei interessato a conoscere il ragionamento per cui gli altri non lo sono, in particolare i suggerimenti grezzi. Ma non è questo lo scopo di questa domanda, immagino.
Lukas Kalbertodt,

1
@LukasKalbertodt Grazie per le informazioni sui tipi primitivi. Ho aggiunto nei tuoi test. Per quanto riguarda la logica dei riferimenti rispetto ai puntatori, dai un'occhiata a questo commento nella richiesta pull di RFC.
SCappella,

Il riferimento non documenta gli attributi instabili, quindi è per questo che non l'hai trovato lì.
Havvy,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.