Salta ai contenuti

Errore Rust E0435: attempt to use a non-constant value in a constant (valore non costante)

E0435 significa: qualcosa che deve essere noto mentre il programma viene compilato cerca di usare un valore che esiste solo mentre il programma gira. Il posto tipico è la lunghezza di un array, come [0.0; days], oppure una const che legge una variabile let. Il codice sembra del tutto ragionevole, e in molti linguaggi funzionerebbe. In Rust le costanti e le lunghezze degli array le calcola il compilatore, prima ancora che il programma parta, quindi non possono guardare le variabili normali. È un errore molto comune, e di solito la correzione è una parola. (Questa pagina fa parte di Errori del compilatore Rust spiegati.)

error[E0435]: attempt to use a non-constant value in a constant
--> src/main.rs:3:26
|
3 | let rainfall = [0.0; days]; // ❌ an array's length must be known at compile time
| ^^^^ non-constant value
|
help: consider using `const` instead of `let`
|
2 - let days = 7;
2 + const days: /* Type */ = 7;
|

Il compilatore dice: stai cercando di usare un valore non costante dentro una costante, e ti consiglia const al posto di let. Questo è il programma che produce l’errore:

fn main() {
let days = 7;
let rainfall = [0.0; days]; // ❌ an array's length must be known at compile time
println!("{:?}", rainfall);
}

Il ^^^^ indica days alla riga 3, dentro [0.0; days]. È la forma «a ripetizione» di un array: sette copie di 0.0. La parte dopo il ; è la lunghezza dell’array, e la lunghezza fa parte del tipo dell’array: [f64; 7] e [f64; 8] sono due tipi diversi. Quindi rustc deve conoscere il numero mentre compila.

Ma days è una variabile let. Per rustc un let è un valore che esiste solo mentre il programma gira, anche quando accanto hai scritto un semplice 7. «Non-constant value» (valore non costante) significa proprio questo.

L’help: mostra la correzione: trasformare la riga 2 in una const. Nota il segnaposto /* Type */: una const ha sempre bisogno del tipo scritto per esteso, quindi scriverai const DAYS: usize = 7;.

La stessa prima riga compare quando una const o una static legge una variabile let, come const PRICE_WITH_TAX: f64 = base_price * 1.2;. In quel caso l’help: suggerisce la direzione opposta: usare let al posto di const.

È come stampare un modulo. Il numero di caselle sul modulo si decide in tipografia, prima che qualcuno lo compili. Non puoi far dipendere il numero di caselle da una risposta che qualcuno scriverà sul modulo più tardi.

Rust ha due momenti: la fase di compilazione, quando cargo build trasforma il tuo codice in un programma, e l’esecuzione, quando il programma gira. Una const, una static e la lunghezza di un array si decidono tutte in fase di compilazione. Il compilatore le scrive direttamente nel programma. Ecco perché sono veloci e perché si possono usare ovunque.

Una variabile let appartiene all’esecuzione. Il suo valore può arrivare da un file, dall’input dell’utente o da un calcolo che avviene solo quando il codice gira. Rust non fa eccezioni per let days = 7; solo perché in questo caso è un letterale: la regola resta semplice, e il tuo codice non si rompe il giorno in cui cambi 7 in read_days_from_config().

Quindi la domanda da farti è: conosco questo numero quando scrivo il codice? Se sì, rendilo una const. Se no, non può essere la lunghezza di un array, e lo strumento giusto è un Vec. Vedi Variabili in Rust e Array in Rust.

Scegli questa quando il numero è fisso nel tuo codice, come i 7 giorni di una settimana. Fai quello che dice l’help:, con due piccole modifiche: scrivi il tipo (usize per una lunghezza) e usa un nome in MAIUSCOLO, che è lo stile di Rust per le costanti:

const DAYS: usize = 7; // ✅ noto in fase di compilazione
fn main() {
let mut rainfall = [0.0; DAYS];
rainfall[2] = 4.5;
rainfall[5] = 1.0;
println!("{:?}", rainfall);
println!("{} giorni", rainfall.len());
}

Output:

[0.0, 0.0, 4.5, 0.0, 0.0, 1.0, 0.0]
7 giorni
▶ Provalo tu

Puoi mettere la const anche dentro main, proprio dove c’era il let. In cima al file, ogni funzione può usarla. Se tieni il nome minuscolo days, compila lo stesso, ma con un warning che ti ricorda che le costanti vanno in maiuscolo.

Soluzione 2: usa un Vec quando la dimensione si conosce solo durante l’esecuzione

Sezione intitolata “Soluzione 2: usa un Vec quando la dimensione si conosce solo durante l’esecuzione”

Scegli questa quando il numero arriva dal programma in esecuzione: la lunghezza di una lista, un valore da un file, l’input dell’utente, un parametro di funzione. Gli array hanno una dimensione fissa; un Vec è la lista che può crescere e avere qualsiasi dimensione. vec![valore; n] è la stessa forma «a ripetizione», e qui n può essere qualsiasi variabile:

fn main() {
let players = ["Ana", "Ben", "Chen"];
let rounds = players.len() * 2;
let mut scores = vec![0; rounds]; // ✅ la lunghezza di un Vec può venire da una variabile
scores[0] = 10;
scores[3] = 7;
println!("{:?}", scores);
println!("{} turni", scores.len());
}

Output:

[10, 0, 0, 7, 0, 0]
6 turni
▶ Provalo tu

Questa è la correzione giusta molto più spesso di quanto si pensi. Un Vec funziona quasi ovunque funzioni un array: indici, .len(), cicli, slice (&scores[1..3]). Per i programmi normali la differenza di velocità è minima. Vedi Vettori in Rust.

Scegli questa quando una const (o una static) legge una variabile let. Questa versione dell’errore ha questo aspetto:

error[E0435]: attempt to use a non-constant value in a constant
--> src/main.rs:3:33
|
3 | const PRICE_WITH_TAX: f64 = base_price * 1.2; // ❌ a `const` can't read a `let` variable
| ^^^^^^^^^^ non-constant value
|
help: consider using `let` instead of `const`

Il programma che non compila:

fn main() {
let base_price = 20.0;
const PRICE_WITH_TAX: f64 = base_price * 1.2; // ❌ a `const` can't read a `let` variable
println!("{:.2}", PRICE_WITH_TAX);
}

Se il valore si calcola mentre il programma gira, è una variabile normale, quindi rendila un let (con un nome minuscolo). Se entrambi i valori sono davvero fissi, funziona anche l’altra strada: rendi const anche il primo. Qui sotto ci sono tutte e due:

const BASE_PRICE: f64 = 20.0;
const PRICE_WITH_TAX: f64 = BASE_PRICE * 1.2; // ✅ una const può usare un'altra const
fn main() {
let base_price = 35.0;
let price_with_tax = base_price * 1.2; // ✅ un let può usare qualsiasi cosa
println!("{:.2}", price_with_tax);
println!("{:.2}", PRICE_WITH_TAX);
}

Output:

42.00
24.00
▶ Provalo tu

La riga help: suggerisce let PRICE_WITH_TAX; compila, ma ricevi un warning che ti ricorda che le variabili vanno in snake_case. Rinominala in price_with_tax.

Soluzione 4: const generics o una const fn, quando ti serve davvero un array

Sezione intitolata “Soluzione 4: const generics o una const fn, quando ti serve davvero un array”

Scegli questa quando vuoi un vero array a dimensione fissa, ma la dimensione deve arrivare da fuori una funzione, o va calcolata. Anche un parametro di funzione è un valore dell’esecuzione, quindi questo dà E0435 (rustc dice che il parametro would need to be a const, dovrebbe essere una const):

error[E0435]: attempt to use a non-constant value in a constant
--> src/main.rs:2:21
|
1 | fn empty_row(size: usize) {
| ---- this would need to be a `const`
2 | let row = [0u8; size]; // ❌ `size` is only known when the function runs
| ^^^^

Il programma che non compila:

fn empty_row(size: usize) {
let row = [0u8; size]; // ❌ `size` is only known when the function runs
println!("{:?}", row);
}
fn main() {
empty_row(3);
}

Un const generic fa quello che chiede rustc: <const N: usize> rende la dimensione un parametro della fase di compilazione, scritto empty_row::<5>() oppure dedotto dal tipo. Una const fn è una funzione che il compilatore può eseguire in fase di compilazione, quindi puoi usarla per calcolare una costante:

fn empty_row<const N: usize>() -> [u8; N] {
[0; N] // ✅ N è un parametro della fase di compilazione
}
const fn cells(width: usize, height: usize) -> usize {
width * height
}
const WIDTH: usize = 4;
const HEIGHT: usize = 3;
const CELLS: usize = cells(WIDTH, HEIGHT); // ✅ calcolato dal compilatore
fn main() {
let row: [u8; 3] = empty_row();
let wide = empty_row::<5>();
println!("{:?}", row);
println!("{:?}", wide);
let board = [0u8; CELLS];
println!("{} caselle", board.len());
}

Output:

[0, 0, 0]
[0, 0, 0, 0, 0]
12 caselle
▶ Provalo tu

Un limite su Rust stabile: dentro una funzione generica puoi usare N da solo come lunghezza, ma non un calcolo che lo contiene, come [u8; N * 2]. Questo dà generic parameters may not be used in const operations e richiede una funzionalità solo nightly. Se sei tu a chiamare empty_row(size) con una size dell’esecuzione, torna alla soluzione 2. Vedi Generici in Rust.

Una dimensione presa da .len() o dall’input dell’utente. let n = names.len(); let done = [false; n]; dà E0435, perché n si conosce solo durante l’esecuzione. Correzione: let done = vec![false; n]; (soluzione 2).

Una static che legge un let. static LINE_WIDTH: usize = width; dà la stessa prima riga, con help: consider using `let` instead of `static` . Anche una static si decide in fase di compilazione, proprio come una const. Correzione: usa let, oppure rendi const anche width.

Una const dentro una funzione generica. In fn bytes<T>() -> usize { const SIZE: usize = std::mem::size_of::<T>(); SIZE }, la const non può usare T. È un codice diverso, E0401, e la correzione segue la stessa idea: let size = std::mem::size_of::<T>();.

Verifica veloce

1. Perché let n = 3; let a = [0; n]; dà E0435, anche se n vale chiaramente 3?

2. La dimensione della tua lista arriva da un file che il programma legge. Qual è la correzione giusta?

3. Quale riga compila?

Punteggio: 0 / 3