Zum Inhalt springen

Rust-Fehler E0401: can't use generic parameters from outer item (innere fn)

E0401 bedeutet: Eine Funktion, die in einer anderen Funktion steht, will den generischen Typ der äußeren benutzen, etwa T, oder das Self des impl, in dem sie sitzt. Es fühlt sich an, als müsste das gehen, schließlich steht die innere Funktion direkt in der äußeren. Aber in Rust ist ein verschachteltes fn ein eigenes Element, das das äußere T nicht sieht. Das überrascht oft, wenn du eine generische Funktion aufräumst und eine Hilfsfunktion in sie hineinziehst. Behoben ist es in einer Minute. Die Seite gehört zur Reihe Rust-Compilerfehler einfach erklärt.

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) { // ❌ inneres `fn` sieht das äußere `T` nicht
| ---- ^ 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) { // ❌ inneres `fn` sieht das äußere `T` nicht
| +++

Auf Deutsch: „Generische Parameter des äußeren Elements können nicht benutzt werden.“ Die Hilfe sagt: „Führe hier einen eigenen generischen Parameter ein.“

Dieses Programm erzeugt den Fehler:

fn print_all<T: std::fmt::Display>(items: &[T]) {
fn show(item: &T) { // ❌ inneres `fn` sieht das äußere `T` nicht
println!("- {}", item);
}
for item in items {
show(item);
}
}
fn main() {
print_all(&["apples", "pears"]);
}

rustc zeigt auf drei Stellen:

  1. Das - unter T in Zeile 1: „type parameter from outer item“ (Typparameter des äußeren Elements). Das ist das T, das zu print_all gehört.
  2. Das ^ unter T in Zeile 2: „use of generic parameter from outer item“ (Benutzung des generischen Parameters des äußeren Elements). Die innere Funktion show versucht, dieses T zu benutzen.
  3. Die ---- unter show: „generic parameter used in this inner function“ (generischer Parameter in dieser inneren Funktion benutzt). Das ist die Funktion, die das nicht darf.

Das note: fasst die Regel in einer Zeile zusammen: Verschachtelte Elemente sind von ihrem Elternelement unabhängig, außer bei Sichtbarkeit und Namensauflösung. Steht fn show in print_all, versteckt das nur seinen Namen vor dem Rest der Datei. Es gibt show keinen Zugriff auf irgendetwas, das zu print_all gehört: nicht auf sein T, nicht auf seine Parameter, nicht auf seine lokalen Variablen.

Das ist wie ein Gästezimmer in einer WG. Das Zimmer liegt in der Wohnung, aber der Gast bekommt nicht den Schlüssel zu deinem Schlafzimmer, nur weil er am selben Flur schläft.

Eine generische Funktion wie print_all<T> ist eine Vorlage. Rust erzeugt für jedes T, mit dem du sie aufrufst, eine eigene Kopie: eine für &str, eine für i32 und so weiter. Ein verschachteltes fn dagegen wird einmal übersetzt, als eigenes Element, genau so, als hättest du es ganz oben in die Datei geschrieben. In show gibt es also kein einzelnes T, das gemeint sein könnte.

Rust hätte verschachtelte Funktionen mit ihrem Elternelement mitkopieren können. Dann würden sie sich aber ganz anders verhalten als jedes andere fn. Stattdessen ist die Regel einfach: Ein inneres fn kennt nur, was du ihm gibst, über seine eigenen generischen Parameter und Argumente. Willst du etwas, das den umgebenden Code doch sieht, hat Rust dafür Closures. Siehe Generics und Closures.

Lösung 1: Der inneren Funktion einen eigenen generischen Parameter geben

Abschnitt betitelt „Lösung 1: Der inneren Funktion einen eigenen generischen Parameter geben“

Das ist meistens die richtige Wahl. Deklariere <T> auch an der inneren Funktion, mit denselben Bounds, die sie braucht. Die Hilfe von rustc schlägt fn show<T> vor. Schreibst du aber nur <T>, bekommst du einen neuen Fehler, E0277 (`T` doesn't implement `std::fmt::Display`), weil das neue T noch keine Bounds hat. Übernimm die Bounds:

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(&["Äpfel", "Birnen"]);
print_all(&[3, 7]);
}

Ausgabe:

- Äpfel
- Birnen
- 3
- 7
▶ Selbst ausprobieren

Das innere T ist ein neuer Parameter, der zufällig genauso heißt. Ruft print_all dann show(item) auf, setzt Rust dafür das äußere T ein. Du könntest ihn auch U nennen, damit das deutlicher wird.

Nimm diese Lösung, wenn die Hilfsfunktion klein ist oder auch die Variablen der äußeren Funktion braucht. Ein Closure ist kein eigenes Element, also sieht es alles um sich herum: das äußere T, die Parameter der Funktion und ihre lokalen Variablen. Hier benutzt das Closure außerdem label, an das ein verschachteltes fn nie herankäme:

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("Obst", &["Äpfel", "Birnen"]);
}

Ausgabe:

Obst: Äpfel
Obst: Birnen
▶ Selbst ausprobieren

Closures können nicht selbst generisch sein, müssen es hier aber auch nicht: In print_all<T> steht &T schon für einen konkreten Typ pro Kopie der Funktion. Siehe Closures.

Lösung 3: Die Funktion nach außen ziehen (oder ins impl)

Abschnitt betitelt „Lösung 3: Die Funktion nach außen ziehen (oder ins impl)“

Nimm diese Lösung, wenn die Hilfsfunktion auch für sich nützlich ist oder wenn der Fehler von Self handelt. In einer Methode kann ein verschachteltes fn auch Self nicht benutzen, weil es auf dieselbe Weise außerhalb des impl steht:

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 { // ❌ inneres `fn` sieht `Self` nicht
| ----- ^^^^ 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 { // ❌ inneres `fn` sieht `Self` nicht
7 + fn count(inv: &Inventory) -> usize { // ❌ inneres `fn` sieht `Self` nicht
|

Hier sagt rustc: „Self des äußeren Elements kann nicht benutzt werden“, und die Hilfe: „Nenne hier stattdessen direkt den Typ.“

Das fehlerhafte Programm:

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

Die Hilfe von rustc (schreib &Inventory) funktioniert. Meist ist es aber sauberer, count eine Ebene nach oben zu ziehen, sodass es eine eigene Methode wird. Dort funktionieren Self und self wie gewohnt:

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

Ausgabe:

2 Gegenstände
▶ Selbst ausprobieren

Bei einer generischen Hilfsfunktion wie show heißt „nach außen ziehen“: ein fn show<T: Display>(item: &T) auf oberster Ebene, das auch andere Funktionen wiederverwenden können. Siehe Structs und Funktionen.

Ein Struct oder Enum in einer generischen Funktion. fn group<T>(items: Vec<T>) mit struct Pair { a: T, b: T } darin ergibt E0401 bei jedem T. Lösung: Mach auch den inneren Typ generisch, struct Pair<T> { a: T, b: T }, oder zieh ihn auf die oberste Ebene.

Eine const, die von T abhängt. const SIZE: usize = std::mem::size_of::<T>(); in einer generischen Funktion ist ebenfalls E0401, denn auch eine const ist ein Element. Lösung: Nimm ein let: let size = std::mem::size_of::<T>();.

Eine innere Funktion, die eine lokale Variable benutzt. Das ist dieselbe Idee, nur mit einem Wert statt einem Typ, aber mit anderem Code: E0434 (can't capture dynamic environment in a fn item, ein fn-Element kann seine Umgebung nicht einfangen). Lösung: Übergib den Wert als Argument, oder nimm ein Closure (Lösung 2).

  • E0411: Self außerhalb jedes impl oder Traits benutzt, etwa in einem fn new() -> Self auf oberster Ebene.
  • E0277: Der Trait Bound ist nicht erfüllt. Das bekommst du nach fn show<T>, wenn du T: Display nicht übernimmst.
  • E0425: Ein Wert oder Typ wird nicht gefunden, etwa ein Typ, den du nie deklariert hast.
  • E0107: falsche Anzahl generischer Argumente, ein anderer Fehler mit Generics.
  • E0434: Ein inneres fn benutzt eine lokale Variable der äußeren Funktion, siehe oben.
  • Alle Compilerfehler: die vollständige Liste der Fehlerseiten.

Kurzer Check

1. Warum kann ein verschachteltes fn show(item: &T) in fn print_all<T>(...) das T nicht benutzen?

2. Welche Hilfsfunktion in fn print_all<T: Display> kann das äußere T und die äußeren lokalen Variablen benutzen?

3. Du folgst der Hilfe von rustc, schreibst fn show<T>(item: &T) und gibst item mit {} aus. Was passiert?

Punkte: 0 / 3