コンテンツにスキップ

Rustのエラー E0401: can't use generic parameters from outer item(外側のジェネリクスは使えない)

E0401 は、関数の中に書いた関数が、外側の関数のジェネリックな型を使おうとしたという意味のエラーです。T のような型や、その関数がある impl の Self です。内側の関数は外側の関数のすぐ中にあるので、使えてもよさそうに感じますよね。でも Rust では、入れ子の fn は独立したアイテムなので、外側の 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 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`
| +++

「can’t use generic parameters from outer item」は「外側のアイテムのジェネリックパラメータは使えない」という意味です。

このエラーが出るのは、次のプログラムです。

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"]);
}

コメントは「内側の fn からは外側の T が見えない」という意味です(エラーになる例なので、コードはそのままにしています)。

rustc は3つの場所を指しています。

  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: は、ルールを1行で言っています。入れ子のアイテムは、親から独立している(公開範囲と名前の解決を除く)、ということです。fn show を print_all の中に置いても、その名前がファイルのほかの部分から隠れるだけです。print_all のものに show がアクセスできるようにはなりません。T も、引数も、ローカル変数もです。

シェアハウスのゲストルームのようなものです。部屋は家の中にありますが、廊下の先で寝ているからといって、ゲストにあなたの寝室の鍵が渡されるわけではありません。

print_all<T> のようなジェネリックな関数は、ひな形です。Rust は、呼び出された T ごとに別々のコピーを作ります。&str 用に1つ、i32 用に1つ、という具合です。ところが入れ子の fn は、それ自身が1つのアイテムとして、1回だけコンパイルされます。ファイルの一番上に書いたのとまったく同じです。だから show の中では、T がどれか1つの型を指すことができないのです。

Rust は、入れ子の関数を親と一緒にコピーするように作ることもできました。でもそうすると、ほかのすべての fn とまったく違う動きになってしまいます。そこでルールはシンプルにしてあります。内側の 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 がすでに1つの具体的な型を指しているからです。クロージャを見てください。

直し方3:関数を外に出す(または impl の中に移す)

Section titled “直し方3:関数を外に出す(または impl の中に移す)”

ヘルパーがそれだけでも役に立つとき、またはエラーが Self についてのときは、これを選びます。メソッドの中の入れ子の fn も、同じように impl の外にいるので、Self を使えません。

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

「can’t use Self from outer item」は「外側のアイテムの Self は使えない」、help は「代わりにここで型を直接書いてください」という意味です。エラーになるプログラムはこちらです(コメントは「内側の 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 の help のとおり &Inventory と書けば動きます。でも、たいていはもっときれいな直し方があります。count を1段上に移して、独立したメソッドにするのです。そこなら、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:トップレベルの 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