Rustのエラー E0790: cannot call associated function on trait(型の指定がない)
E0790 は、型にしか用意できないものをトレイトに頼んでしまった、という意味のエラーです。Default::default() だけを書くのは、「デフォルトをください」と言って、何のデフォルトなのかを言わないようなものです。よく出会うエラーで、たいていは前に型の名前を付ければ直ります。Default::default() の代わりに Settings::default() と書くのです。このページでは、まず直し方を見せてから、トレイトと型の違いを説明します。そうすれば、もうこのエラーに驚かなくなるはずです。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。
エラーメッセージ
Section titled “エラーメッセージ”error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type --> src/main.rs:8:20 |8 | let settings = Default::default(); // ❌ the default of which type? | ^^^^^^^^^^^^^^^^^^ cannot call associated function of trait |help: use a fully-qualified path to a specific available implementation |8 | let settings = </* self type */ as Default>::default(); // ❌ the default of which type? | +++++++++++++++++++ +1行目は「対応する impl の型を指定せずに、トレイトの関連関数を呼ぶことはできない」という意味です。help は「利用できる特定の実装への完全修飾パスを使ってください」と提案しています。
このエラーが出るのは、次のプログラムです。
#[derive(Debug, Default)]struct Settings { volume: u8, dark_mode: bool,}
fn main() { let settings = Default::default(); // ❌ the default of which type? println!("{:?}", settings);}コメントの意味は「❌ どの型のデフォルト?」です。自分で定義したトレイトの関数を、Shape::new() のようにトレイトの名前で呼んだときも、同じエラーになります。次の例では、rustc には Shape を実装している型が2つ見えているので、その両方を候補として挙げてくれます。
error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type --> src/main.rs:33:17 | 2 | fn new() -> Self; | ----------------- `Shape::new` defined here...33 | let shape = Shape::new(); | ^^^^^^^^^^^^ cannot call associated function of trait |help: use a fully-qualified path to one of the available implementations |33 | let shape = <Circle as Shape>::new(); | ++++++++++ +33 | let shape = <Square as Shape>::new(); | ++++++++++ +ここでの help は「利用できる実装のどれか1つへの完全修飾パスを使ってください」という意味です。
Plugin::ID のようなトレイトの定数の場合は、見出しが少し変わって cannot refer to the associated constant on trait without specifying the corresponding `impl` type(対応する impl の型を指定せずに、トレイトの関連定数を参照することはできない)になります。問題も直し方も同じです。
エラーの意味
Section titled “エラーの意味”rustc が印を付けているところを見ていきましょう。
Default::default()の下の^^^^: 「cannot call associated function of trait」(トレイトの関連関数は呼べない)。defaultはDefaultトレイトに属する関数で、それをトレイトの名前を通して呼んでいます。- help は
</* self type */ as Default>::default()を提案しています。/* self type */の部分は、あなたが埋める空欄です。rustc は「どの型のdefaultのこと?」と聞いているのです。 Shapeの例では、 rustc が見つけた型(Circle、Square)で、空欄を埋めてくれています。トレイトを実装している型が1つだけなら、その型だけを提案します。
ここがいちばん大事な考え方です。トレイトは約束です。「Default を実装している型なら、自分の初期値を作れます」という約束です。型は、その約束を守る側です。Settings、String、u32、Vec<T> は、それぞれのやり方でこの約束を守っています。Default::default() は約束そのものに値を求めていますが、約束は何も作れません。作れるのは型だけです。
「テイクアウトできます」という貼り紙を思い浮かべてみてください。たくさんのレストランが、この貼り紙を窓に貼っています。でも、貼り紙に夕食を注文することはできません。どのレストランにするかを選ぶ必要があります。Settings::default() は、レストランを選ぶことにあたります。
Rust がこうする理由
Section titled “Rust がこうする理由”トレイトのメソッドの多くは、circle.area() のように self を受け取ります。これで E0790 になることはありません。値が型を教えてくれるからです。circle は Circle なので、Circle の area が呼ばれます。詳しくはトレイトを見てください。
ところが、トレイトの関数の中には self を受け取らないものがあります。new() や default() のようなコンストラクタ、From::from のような変換、ID のようなトレイトの定数です。見るべき値がないので、Rust が型を知る手がかりは2つしかありません。前に書いた名前(Settings::)か、結果がなるべき型(let s: Settings = ...)です。どちらもなければ、Rust は推測できません。Default を実装している型はたくさんあるので、適当に1つ選んだら、プログラムの動きが知らないうちに変わってしまいます。
これは Java や C# のインターフェースと同じです。インターフェースに対して new はできず、それを実装したクラスが必要です。Rust はそれをコンパイル時に、直すべき場所まで正確に教えてくれるのです。
直し方1:型に対して呼ぶ
Section titled “直し方1:型に対して呼ぶ”ほとんどいつも、これを選びます。トレイトの名前を型の名前に置き換えましょう。いちばん短い直し方で、「デフォルトの Settings」「新しい Circle」と、読んでもわかりやすくなります。
#[derive(Debug, Default)]struct Settings { volume: u8, dark_mode: bool,}
trait Shape { fn new() -> Self; fn name() -> String;}
struct Circle { radius: f64,}
impl Shape for Circle { fn new() -> Self { Circle { radius: 1.0 } } fn name() -> String { String::from("円") }}
fn main() { let settings = Settings::default(); // Default::default() の代わりに let shape = Circle::new(); // Shape::new() の代わりに
println!("{:?}", settings); println!("{}の半径は{}", Circle::name(), shape.radius);}実行結果:
Settings { volume: 0, dark_mode: false }円の半径は1new は impl Circle ブロックではなくトレイトの中で定義されていますが、Circle::new() はちゃんと動きます。Rust は Circle の new を探して、impl Shape for Circle を通して見つけ、それを呼びます。ただし、トレイトがスコープに入っている(同じファイルで定義されているか、use で持ち込まれている)必要があります。
直し方2:変数に型注釈を付ける
Section titled “直し方2:変数に型注釈を付ける”Default::default() という書き方が好きなときや、型の名前が長いときは、これを選びます。変数に型を書けば、Rust はそこから、どの default を呼べばいいかを判断します。
#[derive(Debug, Default)]struct Settings { volume: u8, dark_mode: bool,}
fn main() { let settings: Settings = Default::default(); let small: u8 = 42; let big: u32 = From::from(small);
println!("{:?}", settings); println!("{}", big);}実行結果:
Settings { volume: 0, dark_mode: false }42これが使えるのは、default()、new()、from() のように、関数がその型自身(Self)を返すときだけです。fn name() -> String には使えません。答えが String だとわかっても、どの図形のことなのかは Rust にはわからないからです。そういう関数には、直し方1か直し方3を使いましょう。型注釈について詳しくはデータ型を見てください。
直し方3:完全修飾構文 <Type as Trait>::function()
Section titled “直し方3:完全修飾構文 <Type as Trait>::function()”これは rustc が提案してくる「全部言う」書き方です。<Circle as Shape>::new() は、「Circle が Shape を実装しているから持っている new」と読みます。いつでも使えますが、Circle::new() のほうが読みやすいので、本当に必要なのは、短い書き方ではどれのことか決まらないときだけです。
- その型が、同じ名前の関数を持つ2つのトレイトを実装しているとき
- その型が自分自身の関数として同じ名前のものを持っていて、トレイトのほうを使いたいとき
次の例では、Sale は CSV の行としても、印刷用のレポートとしても書き出せて、どちらのトレイトにも header() があります。Sale::header() と書くと E0034(英語)の「multiple applicable items in scope」(スコープに当てはまるものが複数ある)になるので、どちらなのかを指定します。
trait Csv { fn header() -> String;}
trait Report { fn header() -> String;}
struct Sale;
impl Csv for Sale { fn header() -> String { String::from("date,amount") }}
impl Report for Sale { fn header() -> String { String::from("月間売上") }}
fn main() { println!("{}", <Sale as Csv>::header()); println!("{}", <Sale as Report>::header());}実行結果:
date,amount月間売上(CSV のヘッダーはデータの列名なので、英語のままにしています。)高度なトレイトのレッスンには、&self を受け取るメソッドも使った別の例があります。
直し方4:ジェネリックなコードでは T:: とターボフィッシュを使う
Section titled “直し方4:ジェネリックなコードでは T:: とターボフィッシュを使う”ジェネリックな関数を書いているときは、これを選びます。fn register<T: Plugin>() の中では型は T なので、Plugin::name() ではなく T::name() や T::ID と書きます。関数を呼ぶときは、Rust が型を知るための引数がないので、ターボフィッシュ ::<Type> で型を渡します。
trait Plugin { const ID: u32; fn name() -> String;}
struct Logger;struct Backup;
impl Plugin for Logger { const ID: u32 = 7; fn name() -> String { String::from("logger") }}
impl Plugin for Backup { const ID: u32 = 12; fn name() -> String { String::from("backup") }}
fn register<T: Plugin>() -> String { format!("プラグイン #{}: {}", T::ID, T::name())}
fn main() { println!("{}", register::<Logger>()); println!("{}", register::<Backup>());}実行結果:
プラグイン #7: loggerプラグイン #12: backupターボフィッシュを省いて register() と書くと、E0283「cannot infer type of the type parameter T」(型パラメータ T の型を推論できない)になります。ここでは T も型なので、T:: は直し方1の Circle:: と同じように働きます。型パラメータについて詳しくはジェネリクスを見てください。
よく出会う場面
Section titled “よく出会う場面”手がかりのない Default::default()。 let config = Default::default(); はエラーになりますが、同じ呼び出しでも、型がすでにわかっている場所なら動きます。構造体のフィールド、関数の引数、「残りを埋める」構文などです。
let settings = Settings { volume: 8, ..Default::default() }; // OK:Settings だとわかっているエラーになる行は、Settings::default() か let config: Settings = Default::default(); で直しましょう。よく使うトレイトも見てください。
ジェネリックな関数の中。 T の代わりにトレイトの名前を書いてしまった場合です。
error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type --> src/main.rs:9:29 |2 | fn name() -> String; | -------------------- `Shape::name` defined here...9 | format!("This is a {}", Shape::name()) | ^^^^^^^^^^^^^ cannot call associated function of trait |help: use the fully-qualified path to the only available implementation |9 | format!("This is a {}", <Circle as Shape>::name()) | ++++++++++ +help は「利用できる唯一の実装への完全修飾パスを使ってください」という意味です。気をつけてください。rustc が <Circle as Shape> を提案するのは、今のところ図形が Circle しかないからです。これでコンパイルは通りますが、その関数は T が何であっても、いつも Circle の名前を表示することになります。正しい直し方は、直し方4のように T::name() と書くことです。
トレイトの定数。 println!("{}", Plugin::ID); は約束に数字を求めていますが、数字を持っているのはそれぞれのプラグインだけです。直し方:Logger::ID、ジェネリックなコードなら T::ID と書きます。
変換先のない From::from(x)。 let total = From::from(42u8); は E0790 になります。u8 から作れる型はたくさんあるからです。直し方:u32::from(42u8)、または let total: u32 = 42u8.into(); と書きます。型の変換も見てください。
関連するエラー
Section titled “関連するエラー”- E0282:型注釈が必要(type annotations needed)。
let c = Shape::new();のあとにc.area()のように値を使っていると、rustc は代わりに E0282 を出します。同じ直し方で直ります。 - E0283:当てはまる型がたくさんあるときの「型注釈が必要」。たとえば変換先のない
Into::into(n)や、ターボフィッシュなしで呼んだジェネリックな関数です。 - E0599:関数が見つからない。
SettingsがDefaultを実装していないと、Settings::default()でこのエラーになります。#[derive(Default)]を付けましょう。let s: Settings = Default::default();と書いた場合は、同じ間違いが E0277 として表示されます。 - E0424:
selfパラメータのない関数の中でselfを使った。これも、1つの値ではなく型に属する関数についてのエラーです。 - コンパイルエラーの一覧:エラー解説ページの全リストです。
理解度チェック
Section titled “理解度チェック”理解度チェック
1. let opts = Default::default(); で E0790 が出ました。Options は Default を導出しています。どの行で直りますか?
2. let s: Settings = Default::default(); はコンパイルできるのに、let n: String = Shape::name(); はまだ E0790 になるのはなぜですか?
3. fn show<T: Shape>() の中で、トレイトの関数 name() はどう呼ぶべきですか?
スコア: 0 / 3
さらに詳しく
Section titled “さらに詳しく”- E0790 — Rust error codes index(英語):このエラーの公式の説明です。短い例とその直し方が載っています。
- トレイト:共通の振る舞いを定義する — The Rust Programming Language 日本語版:トレイトとは何か、トレイトの実装のしかた、デフォルト実装を説明している章です。
- 高度なトレイト — The Rust Programming Language 日本語版:完全修飾構文(フルパス記法)の節があり、おなじみの
<Dog as Animal>::baby_name()の例が載っています。 - Disambiguating overlapping traits — Rust By Example 日本語版:同じ名前のメソッドを持つ2つのトレイトから1つを選ぶ、短い実行できる例です。
Default— 標準ライブラリのドキュメント(英語):Defaultの導出、自分での実装、..Default::default()の使い方がわかります。