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 .
&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.