コンテンツにスキップ

Rustのエラー E0790: cannot call associated function on trait(型の指定がない)

E0790 は、型にしか用意できないものをトレイトに頼んでしまった、という意味のエラーです。Default::default() だけを書くのは、「デフォルトをください」と言って、何のデフォルトなのかを言わないようなものです。よく出会うエラーで、たいていは前に型の名前を付ければ直ります。Default::default() の代わりに Settings::default() と書くのです。このページでは、まず直し方を見せてから、トレイトと型の違いを説明します。そうすれば、もうこのエラーに驚かなくなるはずです。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。

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 の型を指定せずに、トレイトの関連定数を参照することはできない)になります。問題も直し方も同じです。

rustc が印を付けているところを見ていきましょう。

  1. Default::default() の下の ^^^^: 「cannot call associated function of trait」(トレイトの関連関数は呼べない)。default は Default トレイトに属する関数で、それをトレイトの名前を通して呼んでいます。
  2. help は </* self type */ as Default>::default() を提案しています。/* self type */ の部分は、あなたが埋める空欄です。rustc は「どの型の default のこと?」と聞いているのです。
  3. Shape の例では、 rustc が見つけた型(Circle、Square)で、空欄を埋めてくれています。トレイトを実装している型が1つだけなら、その型だけを提案します。

ここがいちばん大事な考え方です。トレイトは約束です。「Default を実装している型なら、自分の初期値を作れます」という約束です。型は、その約束を守る側です。Settings、String、u32、Vec<T> は、それぞれのやり方でこの約束を守っています。Default::default() は約束そのものに値を求めていますが、約束は何も作れません。作れるのは型だけです。

「テイクアウトできます」という貼り紙を思い浮かべてみてください。たくさんのレストランが、この貼り紙を窓に貼っています。でも、貼り紙に夕食を注文することはできません。どのレストランにするかを選ぶ必要があります。Settings::default() は、レストランを選ぶことにあたります。

トレイトのメソッドの多くは、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 はそれをコンパイル時に、直すべき場所まで正確に教えてくれるのです。

ほとんどいつも、これを選びます。トレイトの名前を型の名前に置き換えましょう。いちばん短い直し方で、「デフォルトの 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 }
円の半径は1
▶ 実際に試してみよう

new は 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:: と同じように働きます。型パラメータについて詳しくはジェネリクスを見てください。

手がかりのない 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(); と書きます。型の変換も見てください。

  • 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つの値ではなく型に属する関数についてのエラーです。
  • コンパイルエラーの一覧:エラー解説ページの全リストです。

理解度チェック

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