コンテンツにスキップ

Rustのエラー E0615: attempted to take value of method(メソッドをフィールドのように使った)

E0615 は、かっこを付けずに value.something と書いたけれど、something はフィールドではなくメソッドだった、という意味のエラーです。Rust のメソッドは、引数がなくても () を付けて呼んだときにだけ実行されます。C#、JavaScript、Python から来た人がとてもよく出会うエラーです。これらの言語では .Count や .length、プロパティがふつうのフィールドのように見えるからです。直し方は、ほとんどの場合たった2文字です。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。

error[E0615]: attempted to take value of method `len` on type `String`
--> src/main.rs:3:46
|
3 | println!("{} has {} letters", name, name.len); // ❌ `len` はメソッド
| ^^^ method, not a field
|
help: use parentheses to call the method
|
3 | println!("{} has {} letters", name, name.len()); // ❌ `len` はメソッド
| ++

「attempted to take value of method len on type String」は「String 型のメソッド len を値として取り出そうとした」という意味です。method, not a field は「フィールドではなくメソッド」、help は「かっこを付けてメソッドを呼びましょう」と教えてくれています。

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

fn main() {
let name = String::from("Ferris");
println!("{} has {} letters", name, name.len); // ❌ `len` はメソッド
}

見出しは後ろから読むとわかりやすいです。on type String は Rust が調べた型で、method len はそこで見つけたものです。つまり、Rust は len をちゃんと見つけています。ただ、それはフィールドのように読める値ではなく、実行しなければならないコードなのです。

method, not a field というラベルと、help の () の下にある ++ が、まさにそれを伝えています。かっこを付ければ解決です。

自動販売機を思い浮かべてください。フィールドは機械に印刷されたラベル(「12番の自販機」)のようなもので、読むだけで済みます。メソッドはボタン(「おつりを表示」)のようなもので、押すまでは何も起きません。name.len はボタンを指さしているだけで、name.len() でボタンを押すことになります。

Rust では、フィールドとメソッドははっきり分かれています。フィールドは、構造体の中にしまってあるデータです。メソッドは、impl ブロックに書かれた、self を受け取る関数です。フィールドは value.field で読み、メソッドは value.method() で呼びます。詳しくはメソッドで振る舞いを追加するを見てください。

多くの言語では、プロパティがこの境目をあいまいにしています。C# の list.Count や JavaScript の text.length はフィールドのように見えますが、裏ではコードが動いています。Rust には、あえてプロパティがありません。() があればコードが実行される(時間がかかるかもしれない)、なければしまってあるデータを読むだけ、とひと目でわかります。隠れているものは何もありません。

構造体には、非公開の balance フィールドと公開の balance() メソッドのように、同じ名前のフィールドとメソッドを両方持たせることさえできます。これができるのは、Rust がどちらのつもりかを推測しないからです。かっこがなければフィールド、かっこがあればメソッドです。

ほとんどの場合はこれです。help: の行のとおりに、メソッドを呼びましょう。自分で書いたメソッド(ショッピングカートの total など)でも、len、is_empty、trim、count のような標準のメソッドでも、やり方は同じです。

struct Item {
price: u32, // セント単位
qty: u32,
}
struct Cart {
items: Vec<Item>,
}
impl Cart {
fn total(&self) -> u32 {
self.items.iter().map(|i| i.price * i.qty).sum()
}
}
fn main() {
let name = String::from("Ferris");
println!("{} has {} letters", name, name.len()); // ✅ 呼んでいる
let cart = Cart {
items: vec![Item { price: 250, qty: 2 }, Item { price: 100, qty: 1 }],
};
if !cart.items.is_empty() {
println!("合計: {}セント", cart.total()); // ✅ 呼んでいる
}
}

実行結果:

Ferris has 6 letters
合計: 600セント
▶ 実際に試してみよう

(エラーになる例と比べやすいように、1行目の文字列は英語のままにしています。)

メソッドに引数が必要なときは、rustc が _ という目印で引数の場所を教えてくれます。s.contains なら、help には s.contains(_) と表示されます。探したいものを入れて、s.contains("world") のように書きましょう。メソッドに何が必要かわからないときは、次に出るエラー(E0061)が引数の数を教えてくれます。

直し方2:value.method ではなく、メソッドを関数として渡す

Section titled “直し方2:value.method ではなく、メソッドを関数として渡す”

今すぐメソッドを呼びたいのではなく、.map() や sort_by_key のようなものに渡したいときは、これを選びます。.map(words[0].len) と書いても E0615 になります。よい方法は2つあります。メソッドを呼ぶ短いクロージャを書くか、メソッドのフルパス String::len を関数名のように使うかです。

fn main() {
let words = vec![String::from("crab"), String::from("ferris"), String::from("rust")];
// 要素ごとにメソッドを呼ぶクロージャ
let lengths: Vec<usize> = words.iter().map(|w| w.len()).collect();
println!("{:?}", lengths);
// メソッドのパスは関数名のように使える
let same: Vec<usize> = words.iter().map(String::len).collect();
println!("{:?}", same);
let mut sorted = words.clone();
sorted.sort_by_key(|w| w.len());
println!("{:?}", sorted);
}

実行結果:

[4, 6, 4]
[4, 6, 4]
["crab", "rust", "ferris"]
▶ 実際に試してみよう

(文字列の長さを表示する例なので、単語は英語のままにしています。日本語の文字は1文字で3バイトになるからです。)

ここで String::len が使えるのは、iter() が &String の要素を返し、String::len が &String を受け取るからです。型がぴったり合わないときは、クロージャ |w| w.len() を使えば必ずうまくいきます。

直し方3:本当にしまっておくデータなら、フィールドにする

Section titled “直し方3:本当にしまっておくデータなら、フィールドにする”

エラーが自分の構造体で出ていて、その値はただしまっておけばいいと気づいたときは、これを選びます。これは設計の問題です。フィールドにするか、メソッドにするか?

  • 一度決まったら、ほかの何にも左右されない値はフィールドにします。id、name、created_at(作成日)などです。
  • ほかのデータから計算する値はメソッドにします。商品から出す total、幅と高さから出す area、中身から出す len などです。

total をフィールドとしてしまっておくと、誰かが商品を追加して更新し忘れた瞬間に、値が古くなってしまいます。total() メソッドなら毎回計算し直すので、間違うことがありません。次の例では id がフィールドで total がメソッドです。注文が変わっても、メソッドの値が正しいままなのがわかります。

mod shop {
pub struct Order {
pub id: u32, // しまっておく値:一度決めたら order.id で読む
prices: Vec<u32>, // 非公開:メソッドを通してだけ変更する
}
impl Order {
pub fn new(id: u32) -> Self {
Order { id, prices: Vec::new() }
}
pub fn add(&mut self, price: u32) {
self.prices.push(price);
}
// 計算する値:いつも prices と一致し、古くならない
pub fn total(&self) -> u32 {
self.prices.iter().sum()
}
pub fn item_count(&self) -> usize {
self.prices.len()
}
}
}
use shop::Order;
fn main() {
let mut order = Order::new(17);
order.add(250);
order.add(100);
println!("注文{}: {}点、{}セント", order.id, order.item_count(), order.total());
order.add(50);
println!("注文{}: {}点、{}セント", order.id, order.item_count(), order.total());
}

実行結果:

注文17: 2点、350セント
注文17: 3点、400セント
▶ 実際に試してみよう

多くのライブラリで見かける中間的な方法もあります。非公開のフィールドと、同じ名前の公開メソッド(「ゲッター」)の組み合わせです。ほかのコードは acc.balance() で値を読めますが、直接書き換えることはできません。このとき、モジュールの外から acc.balance と書くと、少し違うエラー E0616「field balance of struct Account is private」(構造体 Account のフィールド balance は非公開)が出て、a method `balance` also exists, call it with parentheses(balance というメソッドもあるので、かっこを付けて呼んでください)というヒントが付きます。直し方は同じで、() を付けます。詳しくは何もしなければすべて非公開を見てください。

ほかの言語のくせ。 name.len、list.is_empty、text.trim、words.iter().count は、どれも E0615 になります。Rust ではどれもメソッドなので、どれにも () が必要です。直し方:name.len()、list.is_empty() のように書きます。詳しくはよく使う文字列メソッドを見てください。

Vec の len。 scores.len だけは例外で、E0616「field len of struct Vec is private」(構造体 Vec のフィールド len は非公開)が出ます。Vec には本当に len という隠れたフィールドがあるからです。それでも rustc は help: a method `len` also exists, call it with parentheses と付け加えてくれます。直し方:scores.len()。(scores.is_empty なら、ふつうの E0615 になります。)詳しくはベクタを見てください。

何かを変更するメソッド。 counter.increment; と1行だけ書いても E0615 になります。値がほしいわけではなくても、() がなければ何も実行されないからです。直し方:counter.increment();。

逆のパターン:フィールドをメソッドのように呼ぶ。 name がフィールドなのに user.name() と書くと、E0599 が出て、field, not a method(メソッドではなくフィールド)というラベルと help: remove the arguments(引数を取り除いてください)が表示されます。直し方:user.name。また、on_click: Box<dyn Fn() -> String> のようにフィールドにクロージャが入っているときは、rustc は surround the field access with parentheses(フィールドへのアクセスをかっこで囲んでください)と言います。呼ぶには (button.on_click)() と書きます。

理解度チェック

1. 「attempted to take value of method is_empty on type String」と出ました。どう直しますか?

2. ふつう、しまっておくフィールドではなくメソッドにするべきなのはどれですか?

3. すべての単語の長さがほしいとき、words.iter().map(???) にはどれが使えますか?

スコア: 0 / 3