Errore Rust E0790: cannot call associated function on trait (tipo mancante)
E0790 significa che hai chiesto a un trait qualcosa che solo un tipo può darti. Default::default() da solo è come dire «dammi il valore predefinito» senza dire predefinito di cosa. È un errore comune, e di solito la soluzione è mettere davanti il nome del tipo: Settings::default() invece di Default::default(). Questa pagina mostra prima le soluzioni, poi spiega la differenza tra un trait e un tipo, così questo errore smette di sorprenderti. Fa parte della guida agli errori del compilatore.
Il messaggio di errore
Sezione intitolata “Il messaggio di errore”error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type --> src/main.rs:8:20 |8 | let settings = Default::default(); // ❌ the default of which type? | ^^^^^^^^^^^^^^^^^^ cannot call associated function of trait |help: use a fully-qualified path to a specific available implementation |8 | let settings = </* self type */ as Default>::default(); // ❌ the default of which type? | +++++++++++++++++++ +Il compilatore ti dice: «non si può chiamare una funzione associata su un trait senza specificare il tipo dell’impl corrispondente». E ti suggerisce un percorso completo in cui scrivere tu il tipo.
Ecco il programma che lo provoca:
#[derive(Debug, Default)]struct Settings { volume: u8, dark_mode: bool,}
fn main() { let settings = Default::default(); // ❌ the default of which type? println!("{:?}", settings);}(Il commento dice: il valore predefinito di quale tipo?)
Ottieni lo stesso errore quando chiami la funzione di un tuo trait sul nome del trait, come Shape::new(). Qui rustc vede due tipi che implementano Shape, quindi li elenca entrambi come opzioni:
error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type --> src/main.rs:33:17 | 2 | fn new() -> Self; | ----------------- `Shape::new` defined here...33 | let shape = Shape::new(); | ^^^^^^^^^^^^ cannot call associated function of trait |help: use a fully-qualified path to one of the available implementations |33 | let shape = <Circle as Shape>::new(); | ++++++++++ +33 | let shape = <Square as Shape>::new(); | ++++++++++ +Per una costante di un trait come Plugin::ID, la prima riga cambia un po’: cannot refer to the associated constant on trait without specifying the corresponding `impl` type. Stesso problema, stesse soluzioni.
Cosa significa
Sezione intitolata “Cosa significa”Guarda cosa segna rustc:
^^^^sottoDefault::default(): «cannot call associated function of trait».defaultè una funzione che appartiene al traitDefault, e tu l’hai chiamata attraverso il nome del trait.- L’help suggerisce
</* self type */ as Default>::default(). La parte/* self type */è uno spazio vuoto da riempire. rustc ti sta chiedendo: «ildefaultdi quale tipo intendi?» - Nell’esempio di
Shape, rustc riempie lo spazio per te con ogni tipo che ha trovato (Circle,Square). Se un solo tipo implementa il trait, suggerisce solo quello.
Ecco l’idea chiave. Un trait è una promessa: «ogni tipo che implementa Default sa creare un valore iniziale di sé stesso». Un tipo è chi mantiene la promessa: Settings, String, u32 e Vec<T> la mantengono ognuno a modo suo. Default::default() chiede un valore alla promessa stessa, ma una promessa non può creare niente. Solo un tipo può.
Pensa al cartello «cibo da asporto» sulla vetrina. Lo espongono tanti ristoranti. Ma non puoi ordinare la cena al cartello; devi scegliere un ristorante. Settings::default() è scegliere il ristorante.
Perché Rust si comporta così
Sezione intitolata “Perché Rust si comporta così”La maggior parte dei metodi dei trait riceve self, come circle.area(). Quelli non causano mai E0790, perché il valore dice a Rust il tipo: circle è un Circle, quindi chiama l’area di Circle. Trovi di più in Trait.
Alcune funzioni dei trait non hanno self: costruttori come new() e default(), conversioni come From::from, e le costanti dei trait come ID. Senza un valore da guardare, Rust ha solo due posti da cui capire il tipo: il nome che scrivi davanti (Settings::), oppure il tipo che deve avere il risultato (let s: Settings = ...). Se non c’è nessuno dei due, Rust non può indovinare. Molti tipi implementano Default, e sceglierne uno a caso cambierebbe in silenzio il comportamento del programma.
È come per un’interfaccia in Java o C#. Non puoi fare new su un’interfaccia; ti serve una classe che la implementa. Rust te lo dice semplicemente in fase di compilazione, indicandoti il punto esatto da correggere.
Come risolverlo
Sezione intitolata “Come risolverlo”Soluzione 1: chiamala sul tipo
Sezione intitolata “Soluzione 1: chiamala sul tipo”Scegli questa strada quasi sempre. Sostituisci il nome del trait con il nome del tipo. È la soluzione più breve, e si legge bene: «i Settings predefiniti», «un nuovo Circle»:
#[derive(Debug, Default)]struct Settings { volume: u8, dark_mode: bool,}
trait Shape { fn new() -> Self; fn name() -> String;}
struct Circle { radius: f64,}
impl Shape for Circle { fn new() -> Self { Circle { radius: 1.0 } } fn name() -> String { String::from("cerchio") }}
fn main() { let settings = Settings::default(); // invece di Default::default() let shape = Circle::new(); // invece di Shape::new()
println!("{:?}", settings); println!("Un {} di raggio {}", Circle::name(), shape.radius);}Output:
Settings { volume: 0, dark_mode: false }Un cerchio di raggio 1Circle::new() funziona anche se new è definita nel trait, non in un blocco impl Circle. Rust cerca new su Circle, la trova attraverso impl Shape for Circle e la chiama. Il trait deve essere in scope (definito nello stesso file, o importato con use).
Soluzione 2: annota la variabile
Sezione intitolata “Soluzione 2: annota la variabile”Scegli questa strada quando ti piace lo stile Default::default(), o quando il nome del tipo è lungo. Scrivi il tipo sulla variabile, e Rust capisce da lì quale default chiamare:
#[derive(Debug, Default)]struct Settings { volume: u8, dark_mode: bool,}
fn main() { let settings: Settings = Default::default(); let small: u8 = 42; let big: u32 = From::from(small);
println!("{:?}", settings); println!("{}", big);}Output:
Settings { volume: 0, dark_mode: false }42Funziona solo quando la funzione restituisce il tipo stesso (Self), come fanno default(), new() e from(). Non funziona per fn name() -> String: sapere che la risposta è una String non dice a Rust quale forma intendi. In quei casi usa la Soluzione 1 o la 3. Trovi di più sulle annotazioni in Tipi di dati.
Soluzione 3: la sintassi completa <Tipo as Trait>::funzione()
Sezione intitolata “Soluzione 3: la sintassi completa <Tipo as Trait>::funzione()”È la forma «dico tutto» che suggerisce rustc. Leggi <Circle as Shape>::new() come «la new che Circle ha perché implementa Shape». Funziona sempre, ma Circle::new() si legge meglio, quindi ti serve solo quando la forma breve è ambigua:
- il tipo implementa due trait con una funzione dello stesso nome, oppure
- il tipo ha una sua funzione con quel nome, e tu vuoi la versione del trait.
Qui una Sale (vendita) si può scrivere come riga CSV o come report stampato, ed entrambi i trait hanno un header(). Scrivere Sale::header() dà E0034 (in inglese), «multiple applicable items in scope», quindi devi dire quale:
trait Csv { fn header() -> String;}
trait Report { fn header() -> String;}
struct Sale;
impl Csv for Sale { fn header() -> String { String::from("data,importo") }}
impl Report for Sale { fn header() -> String { String::from("Vendite mensili") }}
fn main() { println!("{}", <Sale as Csv>::header()); println!("{}", <Sale as Report>::header());}Output:
data,importoVendite mensiliLa lezione Trait avanzati ha un altro esempio, anche con metodi che ricevono &self.
Soluzione 4: nel codice generico, usa T:: e il turbofish
Sezione intitolata “Soluzione 4: nel codice generico, usa T:: e il turbofish”Scegli questa strada quando stai scrivendo una funzione generica. Dentro fn register<T: Plugin>() il tipo è T, quindi chiama T::name() e T::ID, non Plugin::name(). Quando chiami la funzione, passa il tipo con il turbofish ::<Tipo>, perché non c’è nessun argomento da cui Rust possa capirlo:
trait Plugin { const ID: u32; fn name() -> String;}
struct Logger;struct Backup;
impl Plugin for Logger { const ID: u32 = 7; fn name() -> String { String::from("logger") }}
impl Plugin for Backup { const ID: u32 = 12; fn name() -> String { String::from("backup") }}
fn register<T: Plugin>() -> String { format!("Plugin n. {}: {}", T::ID, T::name())}
fn main() { println!("{}", register::<Logger>()); println!("{}", register::<Backup>());}Output:
Plugin n. 7: loggerPlugin n. 12: backupSe ometti il turbofish e scrivi register(), ottieni E0283: «cannot infer type of the type parameter T». Anche qui T è il tipo, quindi T:: funziona come Circle:: nella Soluzione 1. Vedi Generici per saperne di più sui parametri di tipo.
Dove lo incontri di solito
Sezione intitolata “Dove lo incontri di solito”Default::default() senza nessun indizio. let config = Default::default(); fallisce, ma la stessa chiamata funziona ovunque il tipo sia già noto: in un campo di struct, in un argomento di funzione, o nella sintassi «compila il resto»:
let settings = Settings { volume: 8, ..Default::default() }; // OK: Rust sa che è SettingsCorreggi la riga che fallisce con Settings::default() oppure let config: Settings = Default::default();. Vedi Trait comuni.
Dentro una funzione generica. Scrivere il nome del trait invece di T:
error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type --> src/main.rs:9:29 |2 | fn name() -> String; | -------------------- `Shape::name` defined here...9 | format!("This is a {}", Shape::name()) | ^^^^^^^^^^^^^ cannot call associated function of trait |help: use the fully-qualified path to the only available implementation |9 | format!("This is a {}", <Circle as Shape>::name()) | ++++++++++ +Attenzione: rustc suggerisce <Circle as Shape> perché per ora Circle è l’unica forma. Compila, ma la funzione direbbe sempre «circle», qualunque sia T. La soluzione giusta è T::name(), come nella Soluzione 4.
Le costanti dei trait. println!("{}", Plugin::ID); chiede un numero alla promessa, ma ce l’ha solo ciascun plugin. Soluzione: Logger::ID, oppure T::ID nel codice generico.
From::from(x) senza destinazione. let total = From::from(42u8); dà E0790, perché molti tipi si possono creare da un u8. Soluzione: u32::from(42u8), oppure let total: u32 = 42u8.into();. Vedi Conversioni.
Errori correlati
Sezione intitolata “Errori correlati”- E0282: servono annotazioni di tipo. Se usi il valore più avanti, come
let c = Shape::new();seguito dac.area(), rustc segnala invece E0282. Valgono le stesse soluzioni. - E0283: servono annotazioni di tipo quando vanno bene molti tipi, per esempio
Into::into(n)senza destinazione, o una funzione generica chiamata senza turbofish. - E0599: nessuna funzione trovata.
Settings::default()dà questo errore seSettingsnon implementaDefault; aggiungi#[derive(Default)]. Conlet s: Settings = Default::default();lo stesso sbaglio si presenta come E0277. - E0424:
selfusato in una funzione senza parametroself. Anche questo riguarda funzioni che appartengono a un tipo e non a un singolo valore. - Tutti gli errori del compilatore: l’elenco completo delle pagine sugli errori.
Verifica veloce
Sezione intitolata “Verifica veloce”Verifica veloce
1. let opts = Default::default(); dà E0790. Options deriva Default. Quale riga lo corregge?
2. Perché let s: Settings = Default::default(); compila, ma let n: String = Shape::name(); dà ancora E0790?
3. Dentro fn show<T: Shape>(), come dovresti chiamare la funzione del trait name()?
Punteggio: 0 / 3
Per approfondire
Sezione intitolata “Per approfondire”- E0790 — Rust error codes index (in inglese): la spiegazione ufficiale di questo errore, con un breve esempio e la sua soluzione.
- Traits — The Rust Book (in inglese): cos’è un trait, come si implementa, e le implementazioni predefinite.
- Advanced Traits — The Rust Book (in inglese): include la sezione sulla sintassi completa, con il classico esempio
<Dog as Animal>::baby_name(). - Disambiguating overlapping traits — Rust by Example (in inglese): un breve esempio eseguibile su come scegliere tra due trait che hanno un metodo con lo stesso nome.
Default— documentazione della libreria standard (in inglese): derivareDefault, implementarlo a mano, e il pattern..Default::default().