Перейти к содержимому

Ошибка Rust E0401: can't use generic parameters from outer item (внешний T)

E0401 значит: функция, написанная внутри другой функции, попыталась использовать обобщённый тип внешней — например, T — или Self того impl, в котором она находится. Кажется, что это должно работать: внутренняя функция ведь прямо внутри внешней. Но в Rust вложенная fn — отдельный элемент, который внешний T не видит. Это частая неожиданность, когда наводишь порядок в обобщённой функции и переносишь вспомогательную функцию внутрь неё, а исправление занимает минуту. Страница входит в раздел Ошибки компилятора 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) { // ❌ 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`
| +++

По-русски: «нельзя использовать обобщённые параметры внешнего элемента». rustc предлагает объявить у внутренней функции собственный обобщённый параметр.

Вот программа, которая её вызывает (комментарий // ❌ оставлен на английском, потому что rustc цитирует эту строку; он значит «внутренняя fn не видит внешний T»):

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 указывает на три места:

  1. - под T в строке 1: «type parameter from outer item» — параметр типа внешнего элемента. Это T, который принадлежит print_all.
  2. ^ под T в строке 2: «use of generic parameter from outer item» — использование обобщённого параметра внешнего элемента. Внутренняя функция show пытается использовать этот T.
  3. ---- под show: «generic parameter used in this inner function» — обобщённый параметр используется в этой внутренней функции. Это та функция, которой так нельзя.

note: формулирует правило в одну строку: вложенные элементы независимы от родителя (во всём, кроме приватности и поиска имён). Если поместить fn show внутрь print_all, её имя просто станет скрыто от остального файла. Доступа к тому, что принадлежит print_all, show от этого не получает: ни к её T, ни к её параметрам, ни к её локальным переменным.

Это как гостевая комната в общем доме. Комната внутри дома, но гость не получает ключ от твоей спальни только потому, что ночует в конце коридора.

Обобщённая функция вроде print_all<T> — это шаблон. Rust делает отдельную копию для каждого T, с которым ты её вызываешь: одну для &str, другую для i32 и так далее. А вложенная fn компилируется один раз, как самостоятельный элемент, ровно так, будто ты написал её в начале файла. Поэтому внутри show нет единственного T, который она могла бы иметь в виду.

Rust мог бы копировать вложенные функции вместе с родителем, но тогда они вели бы себя совсем не так, как любая другая fn. Вместо этого правило простое: внутренняя fn знает только то, что ты ей передал, — через её собственные обобщённые параметры и аргументы. Если нужно что-то, что видит окружающий код, для этого в Rust есть замыкания. Подробнее — в уроках Обобщения и Замыкания.

Исправление 1: Дай внутренней функции собственный обобщённый параметр

Заголовок раздела «Исправление 1: Дай внутренней функции собственный обобщённый параметр»

Это нужно чаще всего. Объяви <T> и у внутренней функции, с теми же ограничениями, которые ей нужны. В help rustc предлагает fn show<T>, но если добавить только <T>, получишь новую ошибку, E0277 (`T` doesn't implement `std::fmt::Display`), потому что у нового T пока нет ограничений. Скопируй ограничения:

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(&["яблоки", "груши"]);
print_all(&[3, 7]);
}

Вывод:

- яблоки
- груши
- 3
- 7
▶ Попробуй сам

Внутренний T — новый параметр, у которого просто такое же имя. Когда print_all вызывает show(item), Rust подставляет в него внешний T. Можно назвать его U, чтобы это было нагляднее.

Выбирай этот вариант, когда вспомогательный код маленький или ему нужны ещё и переменные внешней функции. Замыкание — не отдельный элемент, поэтому оно видит всё вокруг: внешний T, параметры функции и её локальные переменные. Здесь замыкание использует ещё и label, до которого вложенная fn никогда бы не дотянулась:

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("фрукты", &["яблоки", "груши"]);
}

Вывод:

фрукты: яблоки
фрукты: груши
▶ Попробуй сам

Замыкания сами по себе не бывают обобщёнными, но здесь это и не нужно: внутри print_all<T> &T и так означает один конкретный тип в каждой копии функции. Подробнее — в уроке Замыкания.

Исправление 3: Вынеси функцию наружу (или в impl)

Заголовок раздела «Исправление 3: Вынеси функцию наружу (или в impl)»

Выбирай этот вариант, когда вспомогательная функция полезна сама по себе или когда ошибка про Self. Внутри метода вложенная fn тоже не может использовать Self, потому что точно так же находится вне impl:

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`
|

rustc пишет «нельзя использовать Self из внешнего элемента» и предлагает написать тип напрямую. Программа с ошибкой (комментарий значит «внутренняя fn не видит Self»):

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();
}

Подсказка rustc (написать &Inventory) работает. Но обычно чище поднять count на уровень выше, чтобы она стала самостоятельным методом. Там Self и self работают как обычно:

struct Inventory {
items: Vec<String>,
}
impl Inventory {
fn count(&self) -> usize {
self.items.len()
}
fn report(&self) {
println!("предметов: {}", self.count());
}
}
fn main() {
let inv = Inventory {
items: vec!["верёвка".to_string(), "лампа".to_string()],
};
inv.report();
}

Вывод:

предметов: 2
▶ Попробуй сам

Для обобщённой вспомогательной функции вроде show «вынести наружу» значит написать на верхнем уровне fn show<T: Display>(item: &T), которую смогут использовать и другие функции. Подробнее — в уроках Структуры и Функции в Rust.

Структура или перечисление внутри обобщённой функции. fn group<T>(items: Vec<T>) с struct Pair { a: T, b: T } внутри даёт E0401 на каждом T. Исправление: сделай внутренний тип тоже обобщённым, struct Pair<T> { a: T, b: T }, или вынеси его на верхний уровень.

const, который зависит от T. const SIZE: usize = std::mem::size_of::<T>(); внутри обобщённой функции — тоже E0401, потому что const — тоже элемент. Исправление: используй let: let size = std::mem::size_of::<T>();.

Внутренняя функция использует локальную переменную. Та же идея, только со значением вместо типа, — но код другой: E0434 (can't capture dynamic environment in a fn item, «нельзя захватить динамическое окружение в элементе fn»). Исправление: передай значение аргументом или используй замыкание (исправление 2).

  • E0411: Self используется вне любого impl или трейта, например в fn new() -> Self на верхнем уровне.
  • E0277: ограничение трейта не выполнено — это получится после fn show<T>, если не скопировать T: Display.
  • E0425: не найдено значение или тип, например тип, который ты так и не объявил.
  • E0107: неверное число обобщённых аргументов — ещё одна ошибка с обобщениями.
  • E0434: внутренняя fn использует локальную переменную внешней функции, см. выше.
  • Все ошибки компилятора: полный список страниц об ошибках.

Проверь себя

1. Почему внутри fn print_all<T>(...) вложенная fn show(item: &T) не может использовать T?

2. Какой помощник внутри fn print_all<T: Display> может использовать внешний T и внешние локальные переменные?

3. Ты следуешь подсказке rustc и пишешь fn show<T>(item: &T), а потом выводишь item через {}. Что будет?

Результат: 0 / 3