Ir al contenido

Error E0401 de Rust: can't use generic parameters from outer item

E0401 significa que una función escrita dentro de otra función intentó usar el tipo genérico de la de fuera, como T, o el Self del impl donde está. Parece que debería funcionar, porque la función interna está justo ahí dentro. Pero en Rust, una fn anidada es un elemento (item) independiente que no ve el T de fuera. Es una sorpresa habitual cuando ordenas una función genérica metiendo una función auxiliar dentro, y se arregla en un minuto. Forma parte de la guía de errores del compilador.

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) { // ❌ inner `fn` can't see the outer `T`
| ---- ^ 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) { // ❌ inner `fn` can't see the outer `T`
| +++

“Can’t use generic parameters from outer item” quiere decir “no se pueden usar parámetros genéricos del elemento exterior”.

Este es el programa que lo produce:

fn print_all<T: std::fmt::Display>(items: &[T]) {
fn show(item: &T) { // ❌ inner `fn` can't see the outer `T`
println!("- {}", item);
}
for item in items {
show(item);
}
}
fn main() {
print_all(&["apples", "pears"]);
}

rustc señala tres sitios:

  1. El - debajo de T en la línea 1: “type parameter from outer item” (parámetro de tipo del elemento exterior). Es el T que pertenece a print_all.
  2. El ^ debajo de T en la línea 2: “use of generic parameter from outer item” (uso de un parámetro genérico del elemento exterior). La función interna show intenta usar ese T.
  3. El ---- debajo de show: “generic parameter used in this inner function” (parámetro genérico usado en esta función interna). Es la función que no tiene permiso para hacerlo.

La note: resume la regla en una línea: los elementos anidados son independientes de su elemento padre, salvo en la privacidad y en la resolución de nombres. Poner fn show dentro de print_all solo oculta su nombre al resto del archivo. No le da a show acceso a nada de print_all: ni a su T, ni a sus parámetros, ni a sus variables locales.

Es como la habitación de invitados de una casa compartida. La habitación está dentro de la casa, pero el invitado no recibe la llave de tu dormitorio solo porque duerma al fondo del pasillo.

Una función genérica como print_all<T> es una plantilla. Rust crea una copia distinta para cada T con el que la llamas: una para &str, otra para i32, y así. Una fn anidada, en cambio, se compila una sola vez, como un elemento propio, exactamente igual que si la hubieras escrito al principio del archivo. Así que dentro de show no hay un único T al que pueda referirse.

Rust podría haber hecho que las funciones anidadas se copiaran junto con su padre, pero entonces se comportarían de forma muy distinta a cualquier otra fn. En su lugar, la regla es sencilla: una fn interna solo sabe lo que tú le das, mediante sus propios parámetros genéricos y sus argumentos. Si quieres algo que sí vea el código de alrededor, Rust tiene closures para eso. Mira Genéricos y Closures.

Solución 1: Dale a la función interna su propio parámetro genérico

Sección titulada «Solución 1: Dale a la función interna su propio parámetro genérico»

Elige esta casi siempre. Declara <T> también en la función interna, con las mismas restricciones que necesita. El help de rustc sugiere fn show<T>, pero si añades solo <T>, obtienes un error nuevo, E0277 (`T` doesn't implement `std::fmt::Display`), porque el nuevo T todavía no tiene restricciones. Cópialas:

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(&["manzanas", "peras"]);
print_all(&[3, 7]);
}

Salida:

- manzanas
- peras
- 3
- 7
▶ Pruébalo tú mismo

El T interno es un parámetro nuevo que casualmente se llama igual. Cuando print_all llama a show(item), Rust lo rellena con el T de fuera. Podrías llamarlo U para que se vea más claro.

Elige esta cuando la función auxiliar es pequeña, o cuando también necesita las variables de la función externa. Una closure no es un elemento independiente, así que ve todo lo que tiene alrededor: el T de fuera, los parámetros de la función y sus variables locales. Aquí la closure también usa label, algo que una fn anidada nunca podría alcanzar:

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("fruta", &["manzanas", "peras"]);
}

Salida:

fruta: manzanas
fruta: peras
▶ Pruébalo tú mismo

Las closures no pueden ser genéricas por sí solas, pero aquí no les hace falta: dentro de print_all<T>, &T ya es un tipo concreto en cada copia de la función. Mira Closures.

Solución 3: Saca la función fuera (o al impl)

Sección titulada «Solución 3: Saca la función fuera (o al impl)»

Elige esta cuando la función auxiliar es útil por sí misma, o cuando el error es sobre Self. Dentro de un método, una fn anidada tampoco puede usar Self, porque queda fuera del impl de la misma manera:

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 { // ❌ inner `fn` can't see `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 { // ❌ inner `fn` can't see `Self`
7 + fn count(inv: &Inventory) -> usize { // ❌ inner `fn` can't see `Self`
|

“No se puede usar Self del elemento exterior”, y el help propone escribir el tipo directamente. El programa con el error:

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

El help de rustc (escribir &Inventory) funciona. Pero normalmente la solución más limpia es subir count un nivel, para que sea un método propio. Ahí, Self y self funcionan como siempre:

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

Salida:

2 objetos
▶ Pruébalo tú mismo

Para una función auxiliar genérica como show, “sacarla fuera” significa escribir fn show<T: Display>(item: &T) al nivel superior del archivo, donde otras funciones también pueden reutilizarla. Mira Añadir comportamiento con métodos y Funciones en Rust.

Un struct o un enum dentro de una función genérica. fn group<T>(items: Vec<T>) con struct Pair { a: T, b: T } dentro da E0401 en cada T. Solución: haz también genérico el tipo interno, struct Pair<T> { a: T, b: T }, o muévelo al nivel superior.

Un const que depende de T. const SIZE: usize = std::mem::size_of::<T>(); dentro de una función genérica también es E0401, porque un const también es un elemento. Solución: usa un let: let size = std::mem::size_of::<T>();.

Una función interna que usa una variable local. Es la misma idea con un valor en lugar de un tipo, pero con otro código: E0434 (can't capture dynamic environment in a fn item, no se puede capturar el entorno dinámico en una fn). Solución: pasa el valor como argumento, o usa una closure (Solución 2).

  • E0411: Self usado fuera de cualquier impl o trait, por ejemplo en una fn new() -> Self de nivel superior.
  • E0277: no se cumple la restricción del trait; es lo que obtienes con fn show<T> si no copias T: Display.
  • E0425: no se encuentra un valor o un tipo, por ejemplo un tipo que nunca declaraste.
  • E0107: número equivocado de argumentos genéricos, otro error típico con los genéricos.
  • E0434: una fn interna usa una variable local de la función externa, como viste arriba.
  • Todos los errores del compilador: la lista completa de páginas de errores.

Comprueba lo aprendido

1. Dentro de fn print_all<T>(...), ¿por qué una fn show(item: &T) anidada no puede usar T?

2. ¿Qué función auxiliar dentro de fn print_all<T: Display> puede usar el T de fuera y las variables locales de fuera?

3. Sigues el help de rustc y escribes fn show<T>(item: &T), y luego imprimes item con {}. ¿Qué pasa?

Puntuación: 0 / 3