跳转到内容

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

第一行的意思是“不能使用外层项的泛型参数”。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"]);
}

rustc 指出了三个位置:

  1. 第 1 行 T 下面的 -:“type parameter from outer item”(来自外层项的类型参数)。这是属于 print_all 的 T。
  2. 第 2 行 T 下面的 ^:“use of generic parameter from outer item”(使用了外层项的泛型参数)。内部函数 show 想用那个 T。
  3. show 下面的 ----:“generic parameter used in this inner function”(泛型参数在这个内部函数里被使用)。不允许这样做的就是这个函数。

note: 用一句话讲清了规则:嵌套的项和它的父项是相互独立的,只有私有性和名字解析例外。把 fn show 写进 print_all 里,只是让文件的其他地方看不到它的名字而已。这并不会让 show 拿到任何属于 print_all 的东西:拿不到它的 T,拿不到它的参数,也拿不到它的局部变量。

就像合租房里的客房。客房在房子里面,但客人睡在走廊那头,并不代表他就有你卧室的钥匙。

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,这样更清楚。

当辅助函数很小,或者它还需要用到外层函数的变量时,选这个方法。闭包不是独立的项,所以它能看到周围的一切:外层的 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 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`
|

这里说的是“不能使用外层项的 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