Rust 에러 E0401: can't use generic parameters from outer item (바깥 제네릭 사용 불가)
E0401은 함수 안에 쓴 다른 함수가 바깥 함수의 제네릭 타입을 쓰려고 했다는 뜻이에요. T 같은 것이나, 그 함수가 들어 있는 impl의 Self요. 안쪽 함수가 바로 바깥 함수 안에 있으니 될 것 같은 느낌이 들어요. 하지만 Rust에서 중첩된 fn은 별개의 항목(item)이라서 바깥의 T를 볼 수 없어요. 제네릭 함수를 정리하려고 도우미 함수를 안으로 옮길 때 흔히 놀라는 에러인데, 고치는 데는 1분이면 충분해요. 이 페이지는 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) { // ❌ 안쪽 `fn`은 바깥의 `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) { // ❌ 안쪽 `fn`은 바깥의 `T`를 볼 수 없어요 | +++“can’t use generic parameters from outer item”은 “바깥 항목의 제네릭 매개변수를 쓸 수 없다”는 뜻이에요. help:는 “여기에 지역 제네릭 매개변수를 넣어 보라”며 fn show<T>를 제안해요.
이 에러를 내는 프로그램은 다음과 같아요.
fn print_all<T: std::fmt::Display>(items: &[T]) { fn show(item: &T) { // ❌ 안쪽 `fn`은 바깥의 `T`를 볼 수 없어요 println!("- {}", item); }
for item in items { show(item); }}
fn main() { print_all(&["사과", "배"]);}무슨 뜻인가요
섹션 제목: “무슨 뜻인가요”rustc는 세 곳을 가리켜요.
- 1번째 줄
T아래의-: “type parameter from outer item”(바깥 항목의 타입 매개변수).print_all에 속한T예요. - 2번째 줄
T아래의^: “use of generic parameter from outer item”(바깥 항목의 제네릭 매개변수를 사용함). 안쪽 함수show가 그T를 쓰려고 해요. show아래의----: “generic parameter used in this inner function”(이 안쪽 함수에서 제네릭 매개변수를 사용함). 쓰면 안 되는 쪽이 이 함수예요.
note:는 규칙을 한 줄로 말해 줘요. 중첩된 항목은 부모와 독립적이고, 예외는 공개 범위(privacy)와 이름 찾기(name resolution)뿐이라는 거예요. fn show를 print_all 안에 넣으면 그 이름이 파일의 나머지 부분에서 숨겨질 뿐이에요. show가 print_all에 속한 것에 접근할 수 있게 되지는 않아요. T도, 매개변수도, 지역 변수도요.
셰어하우스의 손님방과 비슷해요. 방은 집 안에 있지만, 손님이 복도 끝에서 잔다고 해서 내 침실 열쇠까지 받는 건 아니에요.
Rust가 이렇게 하는 이유
섹션 제목: “Rust가 이렇게 하는 이유”print_all<T> 같은 제네릭 함수는 틀(template)이에요. Rust는 호출할 때 쓰인 T마다 따로 복사본을 만들어요. &str용 하나, i32용 하나, 이런 식으로요. 그런데 중첩된 fn은 파일 맨 위에 쓴 것과 똑같이, 자기만의 항목으로 한 번만 컴파일돼요. 그러니 show 안에서는 T가 가리킬 하나의 타입이 없어요.
Rust가 중첩된 함수도 부모와 함께 복사되게 만들 수도 있었지만, 그러면 다른 모든 fn과 전혀 다르게 동작하게 돼요. 대신 규칙은 단순해요. 안쪽 fn은 내가 넘겨준 것만 알아요. 자기 제네릭 매개변수와 인자를 통해서요. 주변 코드를 볼 수 있는 것이 필요하다면, Rust에는 그걸 위한 클로저가 있어요. 제네릭과 클로저를 참고하세요.
고치는 방법
섹션 제목: “고치는 방법”방법 1: 안쪽 함수에 자기만의 제네릭 매개변수 주기
섹션 제목: “방법 1: 안쪽 함수에 자기만의 제네릭 매개변수 주기”대부분은 이 방법을 고르세요. 안쪽 함수에도 <T>를 선언하되, 필요한 바운드도 똑같이 붙이세요. rustc의 help는 fn show<T>를 제안하지만, <T>만 추가하면 새로운 T에는 아직 바운드가 없어서 새 에러 E0277(`T` doesn't implement `std::fmt::Display`, T가 Display를 구현하지 않는다)이 나요. 바운드를 복사해 오세요.
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 { // ❌ 안쪽 `fn`은 `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 { // ❌ 안쪽 `fn`은 `Self`를 볼 수 없어요7 + fn count(inv: &Inventory) -> usize { // ❌ 안쪽 `fn`은 `Self`를 볼 수 없어요 |“can’t use Self from outer item”은 “바깥 항목의 Self를 쓸 수 없다”는 뜻이고, help:는 “대신 여기서 타입을 직접 가리키라”고 해요. 문제가 되는 프로그램은 이래요.
struct Inventory { items: Vec<String>,}
impl Inventory { fn report(&self) { fn count(inv: &Self) -> usize { // ❌ 안쪽 `fn`은 `Self`를 볼 수 없어요 inv.items.len() } println!("{} items", count(self)); }}
fn main() { let inv = Inventory { items: vec!["rope".to_string()] }; inv.report();}rustc의 help(&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 }를 두면 T마다 E0401이 나요. 해결: 안쪽 타입도 struct Pair<T> { a: T, b: T }처럼 제네릭으로 만들거나, 최상위로 옮기세요.
T에 의존하는 const. 제네릭 함수 안의 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: 최상위의
fn new() -> Self처럼impl이나 트레이트 밖에서Self를 썼어요. - E0277: 트레이트 바운드를 만족하지 않아요.
T: Display를 복사하지 않고fn show<T>만 쓰면 나오는 에러예요. - E0425: 값이나 타입을 찾을 수 없어요. 예를 들어 선언한 적 없는 타입이요.
- E0107: 제네릭 인자 개수가 틀렸어요. 또 다른 제네릭 실수예요.
- E0434: 안쪽
fn이 바깥 함수의 지역 변수를 써요. 위에서 봤어요. - 전체 컴파일러 에러: 에러 페이지 전체 목록이에요.
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. fn print_all<T>(...) 안에 중첩된 fn show(item: &T)가 T를 쓸 수 없는 이유는 무엇일까요?
2. fn print_all<T: Display> 안에서 바깥의 T와 바깥의 지역 변수를 모두 쓸 수 있는 도우미는 무엇일까요?
3. rustc의 help대로 fn show<T>(item: &T)라고 쓰고, item을 {}로 출력해요. 어떻게 될까요?
점수: 0 / 3
더 알아보기
섹션 제목: “더 알아보기”- E0401 — Rust error codes index (영어): 지역 제네릭 매개변수, 클로저, 함수를 밖으로 옮기기라는 세 가지 해결책이 있는 공식 설명이에요.
- Item declarations — The Rust Reference (영어): 안쪽 항목은 바깥 함수의 제네릭 매개변수, 매개변수, 지역 변수를 캡처하지 않는다는 규칙이에요.
- 제네릭 데이터 타입 — The Rust Programming Language 한국어판: 함수, 구조체, 메서드에서
<T>와 트레이트 바운드가 어떻게 동작하는지 설명해요. - 클로저로 환경 캡처하기 — The Rust Programming Language 한국어판: 클로저가 주변 환경을 캡처하는 방법이에요. 중첩된 함수는 할 수 없는 일이에요.