Rustのエラー E0401: can't use generic parameters from outer item(外側のジェネリクスは使えない)
E0401 は、関数の中に書いた関数が、外側の関数のジェネリックな型を使おうとしたという意味のエラーです。T のような型や、その関数がある impl の Self です。内側の関数は外側の関数のすぐ中にあるので、使えてもよさそうに感じますよね。でも Rust では、入れ子の fn は独立したアイテムなので、外側の T は見えません。ジェネリックな関数を整理しようとして、ヘルパー関数を中に入れたときによく驚かされるエラーです。直すのは1分ほどで済みます。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。
エラーメッセージ
Section titled “エラーメッセージ”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` | +++「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 が見えない」という意味です(エラーになる例なので、コードはそのままにしています)。
エラーの意味
Section titled “エラーの意味”rustc は3つの場所を指しています。
- 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: は、ルールを1行で言っています。入れ子のアイテムは、親から独立している(公開範囲と名前の解決を除く)、ということです。fn show を print_all の中に置いても、その名前がファイルのほかの部分から隠れるだけです。print_all のものに show がアクセスできるようにはなりません。T も、引数も、ローカル変数もです。
シェアハウスのゲストルームのようなものです。部屋は家の中にありますが、廊下の先で寝ているからといって、ゲストにあなたの寝室の鍵が渡されるわけではありません。
Rust がこうする理由
Section titled “Rust がこうする理由”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 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` |「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) にするということです。そうすれば、ほかの関数からも使えます。メソッドで振る舞いを追加すると関数を見てください。
よく出会う場面
Section titled “よく出会う場面”ジェネリックな関数の中の構造体や列挙型。 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)。
関連するエラー
Section titled “関連するエラー”- E0411:トップレベルの
fn new() -> Selfのように、implやトレイトの外でSelfを使った。 - E0277:トレイト境界を満たしていない。
T: Displayをコピーせずにfn show<T>と書くと、このエラーになります。 - E0425:値や型が見つからない。たとえば、宣言していない型を使ったとき。
- E0107:ジェネリック引数の数が違う。ジェネリクスの別の間違いです。
- E0434:内側の
fnが外側の関数のローカル変数を使った。上で紹介しました。 - コンパイルエラーの一覧:エラー解説ページの全リストです。
理解度チェック
Section titled “理解度チェック”理解度チェック
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
さらに詳しく
Section titled “さらに詳しく”- E0401 — Rust error codes index(英語):このエラーの公式の説明です。ローカルなジェネリックパラメータ、クロージャ、関数を外に出す、という3つの直し方が載っています。
- Item declarations — The Rust Reference(英語):内側のアイテムは、外側の関数のジェネリックパラメータ、引数、ローカル変数をキャプチャしない、というルールです。
- ジェネリックなデータ型 — The Rust Programming Language 日本語版:関数、構造体、メソッドでの
<T>とトレイト境界の仕組みです。 - クロージャ:環境をキャプチャする匿名関数 — The Rust Programming Language 日本語版:クロージャがどうやって環境をキャプチャするか。入れ子の関数にはできないことです。