Aller au contenu

Erreur Rust E0401 : can't use generic parameters from outer item (T extérieur)

E0401 veut dire qu’une fonction écrite à l’intérieur d’une autre fonction a essayé d’utiliser le type générique de la fonction extérieure, comme T, ou le Self du impl dans lequel elle se trouve. On a l’impression que ça devrait marcher, puisque la fonction interne est juste là, dans la fonction extérieure. Mais en Rust, une fn imbriquée est un élément (item) séparé, qui ne voit pas le T extérieur. C’est une surprise fréquente quand vous rangez une fonction générique en y mettant une petite fonction d’aide, et ça se corrige en une minute. Cette page fait partie du guide des erreurs du compilateur Rust.

error[E0401]: can't use generic parameters from outer item
--> src/main.rs:2:20
|
1 | fn print_all<T: std::fmt::Display>(items: &[T]) {
| - type parameter from outer item
2 | fn show(item: &T) { // ❌ la `fn` interne ne voit pas le `T` extérieur
| ---- ^ use of generic parameter from outer item
| |
| generic parameter used in this inner function
|
= note: nested items are independent from their parent item for everything except for privacy and name resolution
help: try introducing a local generic parameter here
|
2 | fn show<T>(item: &T) { // ❌ la `fn` interne ne voit pas le `T` extérieur
| +++

Le compilateur vous dit : « impossible d’utiliser les paramètres génériques de l’élément extérieur ».

Voici le programme qui la provoque :

fn print_all<T: std::fmt::Display>(items: &[T]) {
fn show(item: &T) { // ❌ la `fn` interne ne voit pas le `T` extérieur
println!("- {}", item);
}
for item in items {
show(item);
}
}
fn main() {
print_all(&["apples", "pears"]);
}

rustc pointe trois endroits :

  1. Le - sous T à la ligne 1 : « type parameter from outer item » (paramètre de type de l’élément extérieur). C’est le T qui appartient à print_all.
  2. Le ^ sous T à la ligne 2 : « use of generic parameter from outer item » (utilisation d’un paramètre générique de l’élément extérieur). La fonction interne show essaie d’utiliser ce T.
  3. Les ---- sous show : « generic parameter used in this inner function » (paramètre générique utilisé dans cette fonction interne). C’est la fonction qui n’en a pas le droit.

La note: donne la règle en une ligne : les éléments imbriqués sont indépendants de leur parent, pour tout sauf la visibilité et la résolution des noms. Mettre fn show dans print_all cache seulement son nom au reste du fichier. Ça ne donne à show l’accès à rien de ce qui appartient à print_all : ni son T, ni ses paramètres, ni ses variables locales.

La ligne help: propose d’ajouter un paramètre générique local, fn show<T>. La solution 1 explique pourquoi ça ne suffit pas tout à fait.

C’est comme une chambre d’amis dans une maison partagée. La chambre est dans la maison, mais l’invité ne reçoit pas la clé de votre chambre juste parce qu’il dort au bout du couloir.

Une fonction générique comme print_all<T> est un modèle. Rust en fait une copie séparée pour chaque T avec lequel vous l’appelez : une pour &str, une pour i32, et ainsi de suite. Une fn imbriquée, elle, est compilée une seule fois, comme un élément à part, exactement comme si vous l’aviez écrite en haut du fichier. Donc, à l’intérieur de show, il n’y a pas un seul T dont elle pourrait parler.

Rust aurait pu faire en sorte que les fonctions imbriquées se copient avec leur parent, mais elles se comporteraient alors très différemment de toutes les autres fn. À la place, la règle est simple : une fn interne ne connaît que ce que vous lui donnez, à travers ses propres paramètres génériques et ses arguments. Si vous voulez quelque chose qui voit le code qui l’entoure, Rust a les closures pour ça. Voir Les génériques et Les closures.

Solution 1 : donner à la fonction interne son propre paramètre générique

Section intitulée « Solution 1 : donner à la fonction interne son propre paramètre générique »

Choisissez-la la plupart du temps. Déclarez <T> sur la fonction interne aussi, avec les mêmes contraintes (bounds) dont elle a besoin. L’aide de rustc propose fn show<T>, mais si vous ajoutez seulement <T>, vous obtenez une nouvelle erreur, E0277 (`T` doesn't implement `std::fmt::Display`, T n’implémente pas Display), parce que le nouveau T n’a pas encore de contraintes. Recopiez les contraintes :

use std::fmt::Display;
fn print_all<T: Display>(items: &[T]) {
fn show<T: Display>(item: &T) {
println!("- {}", item);
}
for item in items {
show(item);
}
}
fn main() {
print_all(&["pommes", "poires"]);
print_all(&[3, 7]);
}

Sortie :

- pommes
- poires
- 3
- 7
▶ Essayez par vous-même

Le T interne est un nouveau paramètre qui porte par hasard le même nom. Quand print_all appelle show(item), Rust le remplit avec le T extérieur. Vous pourriez l’appeler U pour que ce soit plus clair.

Choisissez-la quand la fonction d’aide est petite, ou quand elle a aussi besoin des variables de la fonction extérieure. Une closure n’est pas un élément séparé, donc elle voit tout ce qui l’entoure : le T extérieur, les paramètres de la fonction et ses variables locales. Ici, la closure utilise aussi label, qu’une fn imbriquée ne pourrait jamais atteindre :

use std::fmt::Display;
fn print_all<T: Display>(label: &str, items: &[T]) {
let show = |item: &T| println!("{} : {}", label, item);
for item in items {
show(item);
}
}
fn main() {
print_all("fruit", &["pommes", "poires"]);
}

Sortie :

fruit : pommes
fruit : poires
▶ Essayez par vous-même

Les closures ne peuvent pas être génériques par elles-mêmes, mais ici elles n’en ont pas besoin : à l’intérieur de print_all<T>, &T désigne déjà un seul type concret pour chaque copie de la fonction. Voir Les closures.

Solution 3 : sortir la fonction (ou la mettre dans le impl)

Section intitulée « Solution 3 : sortir la fonction (ou la mettre dans le impl) »

Choisissez-la quand la fonction d’aide est utile toute seule, ou quand l’erreur parle de Self. À l’intérieur d’une méthode, une fn imbriquée ne peut pas utiliser Self non plus, parce qu’elle est en dehors du impl de la même façon :

error[E0401]: can't use `Self` from outer item
--> src/main.rs:7:24
|
5 | impl Inventory {
| ---- `Self` type implicitly declared here, by this `impl`
6 | fn report(&self) {
7 | fn count(inv: &Self) -> usize { // ❌ la `fn` interne ne voit pas `Self`
| ----- ^^^^ use of `Self` from outer item
| |
| `Self` used in this inner function
|
= note: nested items are independent from their parent item for everything except for privacy and name resolution
help: refer to the type directly here instead
|
7 - fn count(inv: &Self) -> usize { // ❌ la `fn` interne ne voit pas `Self`
7 + fn count(inv: &Inventory) -> usize { // ❌ la `fn` interne ne voit pas `Self`
|

Ici, rustc dit « impossible d’utiliser le Self de l’élément extérieur », et l’aide propose d’écrire directement le nom du type.

Le programme qui la provoque :

struct Inventory {
items: Vec<String>,
}
impl Inventory {
fn report(&self) {
fn count(inv: &Self) -> usize { // ❌ la `fn` interne ne voit pas `Self`
inv.items.len()
}
println!("{} items", count(self));
}
}
fn main() {
let inv = Inventory { items: vec!["rope".to_string()] };
inv.report();
}

L’aide de rustc (écrire &Inventory) marche. Mais en général, la correction la plus propre est de remonter count d’un niveau, pour en faire une méthode à part entière. Là, Self et self marchent comme d’habitude :

struct Inventory {
items: Vec<String>,
}
impl Inventory {
fn count(&self) -> usize {
self.items.len()
}
fn report(&self) {
println!("{} objets", self.count());
}
}
fn main() {
let inv = Inventory {
items: vec!["corde".to_string(), "lampe".to_string()],
};
inv.report();
}

Sortie :

2 objets
▶ Essayez par vous-même

Pour une fonction d’aide générique comme show, « la sortir » veut dire écrire une fn show<T: Display>(item: &T) au niveau du fichier, que d’autres fonctions peuvent aussi réutiliser. Voir Les structs et Les fonctions en Rust.

Une struct ou un enum dans une fonction générique. fn group<T>(items: Vec<T>) avec une struct Pair { a: T, b: T } à l’intérieur donne E0401 sur chaque T. Correction : rendez le type interne générique lui aussi, struct Pair<T> { a: T, b: T }, ou sortez-le au niveau du fichier.

Une const qui dépend de T. const SIZE: usize = std::mem::size_of::<T>(); dans une fonction générique donne aussi E0401, parce qu’une const est elle aussi un élément. Correction : utilisez un let : let size = std::mem::size_of::<T>();.

Une fonction interne qui utilise une variable locale. C’est la même idée avec une valeur au lieu d’un type, mais avec un autre code : E0434 (can't capture dynamic environment in a fn item, impossible de capturer l’environnement dynamique dans une fn). Correction : passez la valeur en argument, ou utilisez une closure (solution 2).

  • E0411 : Self utilisé en dehors de tout impl ou trait, par exemple dans une fn new() -> Self au niveau du fichier.
  • E0277 : la contrainte de trait n’est pas satisfaite, ce que vous obtenez après fn show<T> si vous ne recopiez pas T: Display.
  • E0425 : valeur ou type introuvable, par exemple un type que vous n’avez jamais déclaré.
  • E0107 : mauvais nombre d’arguments génériques, une autre erreur liée aux génériques.
  • E0434 : une fn interne utilise une variable locale de la fonction extérieure, voir plus haut.
  • Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.

Petit quiz

1. Dans fn print_all<T>(...), pourquoi une fn show(item: &T) imbriquée ne peut-elle pas utiliser T ?

2. Quelle fonction d'aide dans fn print_all<T: Display> peut utiliser le T extérieur et les variables locales extérieures ?

3. Vous suivez l'aide de rustc et écrivez fn show<T>(item: &T), puis vous affichez item avec {}. Que se passe-t-il ?

Score : 0 / 3

  • E0401 — Rust error codes index (en anglais) : l’explication officielle, avec les trois solutions : un paramètre générique local, une closure, ou sortir la fonction.
  • Item declarations — The Rust Reference (en anglais) : la règle qui dit qu’un élément interne ne capture ni les paramètres génériques, ni les paramètres, ni les variables locales de la fonction extérieure.
  • Generic Data Types — The Rust Book (en anglais) : comment <T> et les contraintes de trait marchent sur les fonctions, les structs et les méthodes.
  • Closures — The Rust Book (en anglais) : comment les closures capturent leur environnement, ce que les fonctions imbriquées ne savent pas faire.