Salta ai contenuti

Errore Rust E0424: expected value, found module `self` (manca self)

E0424 significa che hai scritto self dentro una funzione che non ha un parametro self, quindi non esiste nessun «self» a cui Rust possa riferirsi. È una svista molto comune, soprattutto se arrivi da Java, C# o JavaScript, dove this c’è sempre. La soluzione di solito è una parola: aggiungi &self all’elenco dei parametri della funzione. Questa pagina fa parte della guida agli errori del compilatore.

error[E0424]: expected value, found module `self`
--> src/main.rs:7:32
|
6 | fn describe() {
| -------- this function doesn't have a `self` parameter
7 | println!("Player: {}", self.name); // ❌ which player?
| ^^^^ `self` value is a keyword only available in methods with a `self` parameter
|
help: add a `self` receiver parameter to make the associated `fn` a method
|
6 | fn describe(&self) {
| +++++
error[E0599]: no method named `describe` found for struct `Player` in the current scope
--> src/main.rs:13:10
|
13 | hero.describe();
| ^^^^^^^^ this is an associated function, not a method

Il compilatore dice: «mi aspettavo un valore, ho trovato il modulo self», e spiega che self come valore esiste solo nei metodi con un parametro self. Questo è il programma che lo produce:

struct Player {
name: String,
}
impl Player {
fn describe() {
println!("Player: {}", self.name); // ❌ which player?
}
}
fn main() {
let hero = Player { name: String::from("Ferris") };
hero.describe();
}

Il secondo errore, E0599, è un effetto collaterale del primo. Risolvi E0424 e sparisce anche lui.

Guarda le due etichette che rustc mette sul tuo codice:

  1. Riga 6: fn describe() ha le parentesi vuote. rustc nota che «this function doesn’t have a self parameter» (questa funzione non ha un parametro self).
  2. Riga 7: self.name chiede il nome «del giocatore su cui è stata chiamata». Ma a describe non è stato dato nessun giocatore, quindi qui self non significa niente.

Una funzione dentro un blocco impl senza parametro self è una funzione associata. Appartiene al tipo (Player::describe()), non a un valore particolare. Una funzione con un parametro self è un metodo, e solo i metodi possono dire self.

Pensa a un modulo cartaceo che dice «scrivi qui il tuo nome». Ha senso solo quando qualcuno ha il foglio in mano. describe() è un modulo che non tiene in mano nessuno.

Perché dice «found module self»? In Rust self ha un secondo significato: il modulo corrente, come in use self::helpers;. Quando non c’è un valore self, rustc ripiega su quel significato e poi si lamenta che un modulo non è un valore. Puoi ignorare la parola «module»; il vero problema è il parametro mancante.

In Rust niente è nascosto. Java e C# danno a ogni metodo non statico un this invisibile. Rust ti fa scrivere self come primo parametro, e dici anche come il metodo lo riceve: solo per guardarlo, per cambiarlo o per prenderne l’ownership (possesso). Quella scelta fa parte delle regole del prestito (borrowing), quindi deve essere visibile.

Così chiunque legga il tuo codice, compreso il compilatore, sa esattamente cosa fa un metodo al suo valore. fn summary(&self) promette di non cambiare niente. fn add(&mut self, ...) avvisa che cambia qualcosa. fn checkout(self) consuma il valore. Poi il compilatore controlla quelle promesse al posto tuo. I metodi e i blocchi impl sono spiegati in Struct.

Scegli questa quando la funzione deve lavorare su un valore che esiste già. Nella maggior parte dei casi è la soluzione giusta. Aggiungi il parametro suggerito da rustc e chiama la funzione con il punto:

struct Player {
name: String,
}
impl Player {
fn describe(&self) {
println!("Giocatore: {}", self.name);
}
}
fn main() {
let hero = Player { name: String::from("Ferris") };
hero.describe();
}

Output:

Giocatore: Ferris
▶ Provalo tu

rustc suggerisce sempre &self, ma ne esistono tre tipi. Scegli quello che corrisponde a ciò che fa il metodo:

Parametro Il metodo… Nomi tipici
&self legge soltanto il valore summary, len, is_empty, describe
&mut self cambia il valore add, push, reset, level_up
self consuma il valore (ne prende l’ownership) into_..., finish, build, checkout

Eccoli tutti e tre sullo stesso tipo:

struct Order {
items: Vec<String>,
}
impl Order {
fn summary(&self) -> String {
format!("{} articoli: {}", self.items.len(), self.items.join(", "))
}
fn add(&mut self, item: &str) {
self.items.push(item.to_string());
}
fn checkout(self) -> Vec<String> {
self.items
}
}
fn main() {
let mut order = Order { items: Vec::new() };
order.add("caffè");
order.add("cornetto");
println!("{}", order.summary());
let packed = order.checkout(); // qui order viene consumato
println!("Impacchettato: {:?}", packed);
}

Output:

2 articoli: caffè, cornetto
Impacchettato: ["caffè", "cornetto"]
▶ Provalo tu

Se scegli &self ma il metodo cambia un campo, il compilatore te lo dice subito (E0594 «cannot assign to self.count, which is behind a & reference», oppure E0596 per chiamate come push). Passa a &mut self. Nel dubbio, parti da &self; self è il caso raro.

Una volta che una funzione ha un parametro self, puoi chiamarla in due modi. hero.label() e Player::label(&hero) significano esattamente la stessa cosa: il valore che metteresti prima del punto diventa il primo argomento. La forma con il punto è quella che scriverai quasi sempre. La forma Type:: è comoda quando vuoi passare il metodo stesso a qualcosa come map:

struct Player {
name: String,
level: u32,
}
impl Player {
fn label(&self) -> String {
format!("{} (livello {})", self.name, self.level)
}
}
fn main() {
let hero = Player { name: String::from("Ferris"), level: 3 };
println!("{}", hero.label());
println!("{}", Player::label(&hero)); // la stessa chiamata
let team = vec![hero, Player { name: String::from("Corro"), level: 5 }];
let labels: Vec<String> = team.iter().map(Player::label).collect();
println!("{:?}", labels);
}

Output:

Ferris (livello 3)
Ferris (livello 3)
["Ferris (livello 3)", "Corro (livello 5)"]
▶ Provalo tu

Quello che non funziona è Player::label() senza un valore: un metodo ha sempre bisogno del suo self. Se lo ometti, ottieni E0061, «this function takes 1 argument but 0 arguments were supplied».

Scegli questa quando non c’è ancora un valore, o quando la funzione non sta proprio in un blocco impl.

Il caso classico è un costruttore. new crea il valore, quindi non c’è nessun self da cui leggere. Dai a new i dati iniziali come normali parametri e costruisci la struct con Self { ... }:

struct Counter {
count: u32,
step: u32,
}
impl Counter {
fn new(step: u32) -> Self {
Self { count: 0, step }
}
fn tick(&mut self) {
self.count += self.step;
}
}
fn main() {
let mut counter = Counter::new(5);
counter.tick();
counter.tick();
println!("Conteggio: {}", counter.count);
}

Output:

Conteggio: 10
▶ Provalo tu

Fai caso alle maiuscole: Self (con la S maiuscola) è il tipo, quindi Self { ... } costruisce un nuovo valore. self (con la s minuscola) è il valore su cui è stato chiamato un metodo.

La stessa idea risolve il caso di una funzione libera, fuori da qualsiasi blocco impl. Lì rustc lo dice in modo leggermente diverso: «this function can’t have a self parameter» (questa funzione non può avere un parametro self). Passa invece la struct per riferimento:

struct Cart {
items: Vec<u32>,
}
fn total(cart: &Cart) -> u32 {
cart.items.iter().sum()
}
// chiamala così: total(&cart)

Oppure sposta total dentro impl Cart e dagli &self, come nella Soluzione 1. Vanno bene entrambe; un metodo si legge in modo più naturale quando la funzione riguarda chiaramente un carrello.

Dentro new. Scrivere self.count = 0; in fn new() -> Self dà E0424, e il suggerimento di rustc fn new(&self) -> Self non avrebbe senso (ti servirebbe un contatore per creare un contatore). Soluzione: costruisci il valore con Self { count: 0 }, come nella Soluzione 3.

Dentro una closure in una funzione associata. Il problema non è la closure, ma la funzione che la contiene:

error[E0424]: expected value, found module `self`
--> src/main.rs:8:35
|
7 | fn sale_prices(prices: &[u32]) -> Vec<u32> {
| ----------- this function doesn't have a `self` parameter
8 | prices.iter().map(|p| p - self.discount).collect()
| ^^^^ `self` value is a keyword only available in methods with a `self` parameter

Soluzione: rendila un metodo, fn sale_prices(&self, prices: &[u32]), oppure passa il numero come parametro, fn sale_prices(prices: &[u32], discount: u32). Vedi Closure.

In main o in una funzione di supporto. self non funziona mai fuori da un blocco impl. Soluzione: passa la struct come parametro (total(&cart)), oppure chiama un metodo su di lei (cart.total()).

Se arrivi dai metodi «static» di C# o Java. Una funzione associata senza self è la versione Rust di un metodo statico. Se vuoi la versione di istanza, devi aggiungere tu il parametro self. Vedi Rust per sviluppatori C#.

  • E0425: la svista opposta. Sei in un metodo, ma hai scritto count invece di self.count.
  • E0599: «no method named … found». Spesso stampato subito dopo E0424, perché una funzione senza self non si può chiamare con il punto.
  • E0061: numero sbagliato di argomenti, per esempio quando chiami Player::label() senza il valore.
  • E0596: hai aggiunto &self, ma il metodo cambia un campo, quindi gli serve &mut self. Assegnare un campo in questo modo dà E0594.
  • Tutti gli errori del compilatore: l’elenco completo delle pagine sugli errori.

Verifica veloce

1. Dentro impl Player, fn heal() { self.hp += 10; } dà E0424. Qual è la soluzione migliore?

2. Perché fn new() -> Self non può usare self.name?

3. Su Player è definito fn label(&self) -> String. Quale chiamata equivale a hero.label()?

Punteggio: 0 / 3