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.
Le message d’erreur
Section intitulée « Le message d’erreur »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);}Ce que ça veut dire
Section intitulée « Ce que ça veut dire »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.
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »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 ?.
Comment corriger
Section intitulée « Comment corriger »Solution 1 : faire de la taille une const
Section intitulée « Solution 1 : faire de la taille une const »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 joursVous 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 manchesC’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.
Solution 3 : utiliser let au lieu de const
Section intitulée « Solution 3 : utiliser let au lieu de const »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.0024.00La 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 casesUne 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.
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent »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>();.
Erreurs liées
Section intitulée « Erreurs liées »- E0434 : can’t capture dynamic environment in a fn item : la même règle « les éléments ne voient pas les variables locales », pour une
fnécrite dans une autre fonction. - E0401 : can’t use generic parameters from outer item : une
fnou uneconstimbriquée qui utilise leTde la fonction extérieure. - E0015 :
cannot call non-const function `args` in constants, quand uneconstappelle une fonction normale commestd::env::args()au lieu de lire une variable. Solution :let, ou uneconst fn. - E0308 : mismatched types : ce que vous obtenez après la solution 1 si un tableau de la mauvaise taille arrive là où
[f64; DAYS]est attendu. - E0658 : use of unstable feature : si vous allez chercher des fonctionnalités
constréservées à nightly pour contourner le problème. - Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.
Petit quiz
Section intitulée « Petit quiz »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
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0435 — Rust error codes index (en anglais) : l’explication officielle, avec la solution
const. - Constants — The Rust Book (en anglais) : en quoi
constdiffère delet, et pourquoi les constantes ont besoin d’un type. - Constant evaluation — The Rust Reference (en anglais) : exactement quelles expressions le compilateur sait calculer à la compilation, et où elles sont obligatoires (tailles de tableaux,
const,static). - Storing Lists of Values with Vectors — The Rust Book (en anglais) : la liste extensible à utiliser quand la taille n’est connue qu’à l’exécution.
- Const generics MVP — Rust Blog (en anglais) : comment marche
<const N: usize>, avec des exemples de tableaux, et ce que Rust stable sait faire ou non avec.