Aller au contenu

Erreur Rust E0435 : attempt to use a non-constant value in a constant

E0435 veut dire : quelque chose qui doit être connu pendant la compilation de votre programme essaie d’utiliser une valeur qui n’existe que pendant son exécution. Le cas le plus courant, c’est une taille de tableau, comme [0.0; days], ou une const qui lit une variable let. Le code a l’air tout à fait raisonnable, et dans beaucoup de langages il marcherait. En Rust, les constantes et les tailles de tableaux sont calculées par le compilateur, avant même que le programme démarre : elles ne peuvent donc pas regarder des variables ordinaires. C’est une erreur très fréquente, et la correction tient souvent en un mot. Cette page fait partie du guide des erreurs du compilateur Rust.

error[E0435]: attempt to use a non-constant value in a constant
--> src/main.rs:3:26
|
3 | let rainfall = [0.0; days]; // ❌ la taille d'un tableau doit être connue à la compilation
| ^^^^ non-constant value
|
help: consider using `const` instead of `let`
|
2 - let days = 7;
2 + const days: /* Type */ = 7;
|

Le compilateur vous dit : « tentative d’utiliser une valeur non constante dans une constante », et il propose d’utiliser const au lieu de let.

Voici le programme qui la provoque :

fn main() {
let days = 7;
let rainfall = [0.0; days]; // ❌ la taille d'un tableau doit être connue à la compilation
println!("{:?}", rainfall);
}

Les ^^^^ pointent sur days à la ligne 3, dans [0.0; days]. C’est la forme « répétition » d’un tableau : sept copies de 0.0. Ce qui vient après le ; est la taille du tableau, et cette taille fait partie de son type : [f64; 7] et [f64; 8] sont deux types différents. rustc doit donc connaître ce nombre au moment de compiler.

Mais days est une variable let. Pour rustc, un let est une valeur qui n’existe que pendant l’exécution du programme, même si vous avez écrit un simple 7 à côté. « Non-constant value » (valeur non constante) veut dire exactement ça.

Le help: montre la solution : transformer la ligne 2 en const. Remarquez le paramètre fictif /* Type */ : une const doit toujours avoir son type écrit, vous écrirez donc const DAYS: usize = 7;.

Le même titre d’erreur apparaît quand une const ou un static lit une variable let, comme const PRICE_WITH_TAX: f64 = base_price * 1.2;. Cette fois, le help: propose le chemin inverse : utiliser let au lieu de const.

C’est comme un formulaire imprimé. Le nombre de cases est décidé chez l’imprimeur, avant que quiconque le remplisse. Vous ne pouvez pas faire dépendre le nombre de cases d’une réponse que quelqu’un écrira plus tard sur le formulaire.

Rust a deux moments : la compilation, quand cargo build transforme votre code en programme, et l’exécution, quand le programme tourne. Une const, un static et la taille d’un tableau sont tous décidés à la compilation. Le compilateur les écrit directement dans le programme. C’est pour ça qu’ils sont rapides et qu’on peut les utiliser partout.

Une variable let appartient à l’exécution. Sa valeur peut venir d’un fichier, de ce que tape l’utilisateur, ou d’un calcul qui n’a lieu que quand le code s’exécute. Rust ne fait pas d’exception pour let days = 7; sous prétexte que, cette fois, c’est un nombre écrit en dur : la règle reste simple, et votre code ne casse pas le jour où vous remplacez 7 par read_days_from_config().

La question à se poser est donc : est-ce que je connais ce nombre au moment où j’écris le code ? Si oui, faites-en une const. Si non, il ne peut pas servir de taille de tableau, et un Vec est le bon outil. Voir Les constantes et Tableaux ou vecteurs ?.

Choisissez-la quand le nombre est fixé dans votre code, comme les 7 jours d’une semaine. Faites ce que dit le help:, avec deux petits changements : écrivez le type (usize pour une taille) et donnez un nom en MAJUSCULES, le style Rust pour les constantes :

const DAYS: usize = 7; // ✅ connue à la compilation
fn main() {
let mut rainfall = [0.0; DAYS];
rainfall[2] = 4.5;
rainfall[5] = 1.0;
println!("{:?}", rainfall);
println!("{} jours", rainfall.len());
}

Sortie :

[0.0, 0.0, 4.5, 0.0, 0.0, 1.0, 0.0]
7 jours
▶ Essayez par vous-même

Vous pouvez aussi mettre la const dans main, à la place du let. En haut du fichier, toutes les fonctions peuvent l’utiliser. Si vous gardez le nom en minuscules days, ça compile quand même, mais avec un avertissement : les constantes devraient être en majuscules.

Solution 2 : utiliser un Vec quand la taille n’est connue qu’à l’exécution

Section intitulée « Solution 2 : utiliser un Vec quand la taille n’est connue qu’à l’exécution »

Choisissez-la quand le nombre vient du programme en cours d’exécution : la longueur d’une liste, une valeur lue dans un fichier, ce que tape l’utilisateur, un paramètre de fonction. Les tableaux ont une taille fixe ; un Vec est la liste extensible qui peut avoir n’importe quelle taille. vec![valeur; n] est la même forme « répétition », et ici n peut être n’importe quelle variable :

fn main() {
let players = ["Ana", "Ben", "Chen"];
let rounds = players.len() * 2;
let mut scores = vec![0; rounds]; // ✅ la taille d'un Vec peut venir d'une variable
scores[0] = 10;
scores[3] = 7;
println!("{:?}", scores);
println!("{} manches", scores.len());
}

Sortie :

[10, 0, 0, 7, 0, 0]
6 manches
▶ Essayez par vous-même

C’est la bonne solution bien plus souvent qu’on ne le croit. Un Vec marche presque partout où un tableau marche : l’indexation, .len(), les boucles, les slices (&scores[1..3]). La différence de vitesse est minuscule pour un programme normal. Voir Les vecteurs.

Choisissez-la quand une const (ou un static) lit une variable let. Cette version de l’erreur ressemble à ça :

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; // ❌ une `const` ne peut pas lire une variable `let`
| ^^^^^^^^^^ non-constant value
|
help: consider using `let` instead of `const`

Le programme qui la provoque :

fn main() {
let base_price = 20.0;
const PRICE_WITH_TAX: f64 = base_price * 1.2; // ❌ une `const` ne peut pas lire une variable `let`
println!("{:.2}", PRICE_WITH_TAX);
}

Si la valeur est calculée pendant l’exécution du programme, c’est une variable normale : faites-en un let (avec un nom en minuscules). Si les deux valeurs sont vraiment fixes, l’autre sens marche aussi : faites aussi de la première une const. Voici les deux :

const BASE_PRICE: f64 = 20.0;
const PRICE_WITH_TAX: f64 = BASE_PRICE * 1.2; // ✅ une const peut utiliser une autre const
fn main() {
let base_price = 35.0;
let price_with_tax = base_price * 1.2; // ✅ un let peut utiliser n'importe quoi
println!("{:.2}", price_with_tax);
println!("{:.2}", PRICE_WITH_TAX);
}

Sortie :

42.00
24.00
▶ Essayez par vous-même

La ligne help: propose let PRICE_WITH_TAX ; ça compile, mais vous aurez un avertissement : les variables devraient être en snake_case. Renommez-la en price_with_tax.

Solution 4 : const generics ou const fn, quand il vous faut vraiment un tableau

Section intitulée « Solution 4 : const generics ou const fn, quand il vous faut vraiment un tableau »

Choisissez-la quand vous voulez un vrai tableau de taille fixe, mais que la taille doit venir de l’extérieur d’une fonction, ou être calculée. Un paramètre de fonction est lui aussi une valeur d’exécution, donc ceci donne E0435 (rustc dit que le paramètre would need to be a const, « devrait être une 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` n'est connu qu'à l'exécution de la fonction
| ^^^^

Le programme qui la provoque :

fn empty_row(size: usize) {
let row = [0u8; size]; // ❌ `size` n'est connu qu'à l'exécution de la fonction
println!("{:?}", row);
}
fn main() {
empty_row(3);
}

Un const generic (paramètre générique constant) fait ce que demande rustc : <const N: usize> fait de la taille un paramètre connu à la compilation, qu’on écrit empty_row::<5>() ou que Rust déduit du type. Une const fn est une fonction que le compilateur peut exécuter pendant la compilation : vous pouvez donc vous en servir pour calculer une constante :

fn empty_row<const N: usize>() -> [u8; N] {
[0; N] // ✅ N est un paramètre connu à la compilation
}
const fn cells(width: usize, height: usize) -> usize {
width * height
}
const WIDTH: usize = 4;
const HEIGHT: usize = 3;
const CELLS: usize = cells(WIDTH, HEIGHT); // ✅ calculée par le compilateur
fn main() {
let row: [u8; 3] = empty_row();
let wide = empty_row::<5>();
println!("{:?}", row);
println!("{:?}", wide);
let board = [0u8; CELLS];
println!("{} cases", board.len());
}

Sortie :

[0, 0, 0]
[0, 0, 0, 0, 0]
12 cases
▶ Essayez par vous-même

Une limite en Rust stable : dans une fonction générique, vous pouvez utiliser N seul comme taille, mais pas un calcul avec lui, comme [u8; N * 2]. Ça donne generic parameters may not be used in const operations (les paramètres génériques ne peuvent pas servir dans des opérations constantes), et il faut une fonctionnalité réservée à nightly. Si c’est vous qui appelez empty_row(size) avec une size connue seulement à l’exécution, revenez à la solution 2. Voir Les génériques.

Une taille qui vient de .len() ou de l’utilisateur. let n = names.len(); let done = [false; n]; donne E0435, parce que n n’est connu qu’à l’exécution. Solution : let done = vec![false; n]; (solution 2).

Un static qui lit un let. static LINE_WIDTH: usize = width; donne le même titre, avec help: consider using `let` instead of `static` . Un static est décidé à la compilation, exactement comme une const. Solution : utilisez let, ou faites aussi de width une const.

Une const dans une fonction générique. Dans fn bytes<T>() -> usize { const SIZE: usize = std::mem::size_of::<T>(); SIZE }, la const ne peut pas utiliser T. C’est un autre code, E0401, et la solution suit la même idée : let size = std::mem::size_of::<T>();.

Petit quiz

1. Pourquoi let n = 3; let a = [0; n]; donne-t-il E0435, alors que n vaut clairement 3 ?

2. La taille de votre liste vient d'un fichier que le programme lit. Quelle est la bonne solution ?

3. Quelle ligne compile ?

Score : 0 / 3