Ошибка 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 item2 | 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 resolutionhelp: 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 указывает на три места:
-подTв строке 1: «type parameter from outer item» — параметр типа внешнего элемента. ЭтоT, который принадлежитprint_all.^подTв строке 2: «use of generic parameter from outer item» — использование обобщённого параметра внешнего элемента. Внутренняя функцияshowпытается использовать этотT.----подshow: «generic parameter used in this inner function» — обобщённый параметр используется в этой внутренней функции. Это та функция, которой так нельзя.
note: формулирует правило в одну строку: вложенные элементы независимы от родителя (во всём, кроме приватности и поиска имён). Если поместить fn show внутрь print_all, её имя просто станет скрыто от остального файла. Доступа к тому, что принадлежит print_all, show от этого не получает: ни к её T, ни к её параметрам, ни к её локальным переменным.
Это как гостевая комната в общем доме. Комната внутри дома, но гость не получает ключ от твоей спальни только потому, что ночует в конце коридора.
Почему Rust так делает
Заголовок раздела «Почему Rust так делает»Обобщённая функция вроде 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, чтобы это было нагляднее.
Исправление 2: Используй замыкание
Заголовок раздела «Исправление 2: Используй замыкание»Выбирай этот вариант, когда вспомогательный код маленький или ему нужны ещё и переменные внешней функции. Замыкание — не отдельный элемент, поэтому оно видит всё вокруг: внешний 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 resolutionhelp: 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
Что почитать
Заголовок раздела «Что почитать»- E0401 — Rust error codes index (на английском): официальное объяснение с тремя исправлениями: свой обобщённый параметр, замыкание или вынос функции наружу.
- Item declarations — The Rust Reference (на английском): правило, по которому внутренний элемент не захватывает обобщённые параметры, параметры и локальные переменные внешней функции.
- Обобщённые типы данных — книга «Язык программирования Rust» (перевод на русский): как
<T>и ограничения трейтов работают в функциях, структурах и методах. - Замыкания — книга «Язык программирования Rust» (перевод на русский): как замыкания захватывают окружение — то, чего вложенные функции не умеют.