Rust 错误 E0401:can't use generic parameters from outer item(不能用外层项的泛型)
E0401 的意思是:写在另一个函数里面的函数,想用外层函数的泛型类型,比如 T,或者它所在 impl 的 Self。感觉上这应该行得通,毕竟内部函数就写在外层函数里面。但在 Rust 里,嵌套的 fn 是一个独立的项(item),看不到外层的 T。当你为了整理一个泛型函数,把辅助函数挪进去的时候,经常会被它吓一跳,好在一分钟就能修好。本页属于编译错误指南。
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` | +++第一行的意思是“不能使用外层项的泛型参数”。help: 建议在这里引入一个局部的泛型参数。
产生这个错误的程序是这样的:
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"]);}这个错误是什么意思
Section titled “这个错误是什么意思”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: 用一句话讲清了规则:嵌套的项和它的父项是相互独立的,只有私有性和名字解析例外。把 fn show 写进 print_all 里,只是让文件的其他地方看不到它的名字而已。这并不会让 show 拿到任何属于 print_all 的东西:拿不到它的 T,拿不到它的参数,也拿不到它的局部变量。
就像合租房里的客房。客房在房子里面,但客人睡在走廊那头,并不代表他就有你卧室的钥匙。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”print_all<T> 这样的泛型函数是一个模板。你用什么 T 调用它,Rust 就为那个 T 单独生成一份:&str 一份,i32 一份,依此类推。而嵌套的 fn 只编译一次,作为一个独立的项,和你把它写在文件最外层完全一样。所以在 show 里面,T 没有一个确定的含义。
Rust 本可以让嵌套函数跟着父函数一起复制,但那样它们的行为就会和其他所有 fn 大不相同。Rust 选择了一条简单的规则:内部 fn 只知道你通过它自己的泛型参数和实参交给它的东西。如果你想要一个能看到周围代码的东西,Rust 有闭包。参见泛型和闭包。
修复 1:给内部函数加上它自己的泛型参数
Section titled “修复 1:给内部函数加上它自己的泛型参数”大多数时候选这个方法。在内部函数上也声明 <T>,并带上它需要的约束。rustc 的 help 建议写 fn show<T>,但如果只加 <T>,你会得到一个新错误 E0277(`T` doesn't implement `std::fmt::Display`,T 没有实现 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:改用闭包
Section titled “修复 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)
Section titled “修复 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` |这里说的是“不能使用外层项的 Self”,help: 建议直接写出类型名。出错的程序:
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 的 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),这样其他函数也能复用它。参见结构体和函数。
泛型函数里的结构体或枚举。 在 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:在任何
impl或 trait 之外使用了Self,比如在最外层写fn new() -> Self。 - E0277:不满足 trait 约束,写了
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 的 help 写了 fn show<T>(item: &T),然后用 {} 打印 item。会发生什么?
得分: 0 / 3
- E0401 — Rust error codes index(英文):官方解释,附有三种修法:局部泛型参数、闭包,或者把函数移到外面。
- Item declarations — The Rust Reference(英文):内部项不会捕获外层函数的泛型参数、参数或局部变量的规则。
- 泛型数据类型 — Rust 程序设计语言:
<T>和 trait 约束在函数、结构体和方法上怎么用。 - 闭包 — Rust 程序设计语言:闭包怎样捕获它的环境,这是嵌套函数做不到的。