メソッド
メソッドは関数に似ています。fn キーワードと名前で宣言でき、引数と戻り値を
持つことができ、メソッドがほかの場所から呼び出されたときに実行される
コードを含みます。関数とは異なり、メソッドは構造体(または enum や
トレイトオブジェクト。これらはそれぞれ 第6章 と 第
18章 で扱います)のコンテキスト内で定義され、
その第1引数は常に self です。これは、そのメソッドが呼び出される対象で
ある構造体のインスタンスを表します。
メソッド構文
引数として Rectangle インスタンスを受け取る area 関数を変更し、代わりに
Rectangle 構造体に定義された area メソッドにしてみましょう。これは
リスト5-13に示されています。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
}
Rectangle のコンテキスト内で関数を定義するために、まず Rectangle 用の
impl(実装)ブロックを開始します。この impl ブロック内にあるものは
すべて Rectangle 型に関連付けられます。次に、area 関数を impl の
波括弧の内側に移し、シグネチャおよび本体内のすべての箇所で、第1引数
(この場合は唯一の引数)を self に変更します。main では、area
関数を呼び出して rect1 を引数として渡していましたが、代わりに
メソッド構文 を使って Rectangle インスタンスに対して area
メソッドを呼び出せます。メソッド構文はインスタンスの後ろに続けて
書きます。ドットの後にメソッド名、丸括弧、必要なら引数を続けます。
area のシグネチャでは、rectangle: &Rectangle の代わりに &self を
使います。&self は実際には self: &Self の短縮形です。impl
ブロック内では、型 Self はその impl ブロックが対象としている型の
エイリアスです。メソッドは第1引数として、型 Self の self という
名前の引数を持たなければならないため、Rust では第1引数の位置では
名前 self だけでこれを省略して書けます。ここで使った
rectangle: &Rectangle と同じく、このメソッドが Self
インスタンスを借用することを示すために、self
の短縮記法の前に & を付ける必要があることに注意してください。
メソッドは、ほかの引数と同じように、self の所有権を取得したり、
ここで行っているように self を不変で借用したり、あるいは self
を可変で借用したりできます。
ここで &self を選んだのは、関数版で &Rectangle
を使ったのと同じ理由です。所有権は取得したくなく、構造体のデータを
読み取りたいだけで、書き込みたくはないからです。もしメソッドの処理の
一部として、メソッドを呼び出したインスタンスを変更したいなら、第1引数
として &mut self を使います。第1引数に単なる self
を使ってインスタンスの所有権を取得するメソッドはまれです。この手法は
通常、メソッドが self
を別のものに変換するときに使われ、変換後に呼び出し元が元の
インスタンスを使えないようにしたい場合に用いられます。
関数ではなくメソッドを使う主な理由は、メソッド構文を提供でき、
各メソッドのシグネチャで毎回 self の型を繰り返さなくてよいことに
加えて、整理のためです。将来このコードを使うユーザーが、私たちが提供
するライブラリのさまざまな場所で Rectangle
の機能を探し回らなくて済むように、ある型のインスタンスに対して
できることをすべて1つの impl ブロックにまとめたのです。
構造体のフィールドの1つと同じ名前をメソッドに付けることもできる点に
注意してください。たとえば、Rectangle に width
という名前のメソッドを定義できます。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn width(&self) -> bool {
self.width > 0
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
if rect1.width() {
println!("The rectangle has a nonzero width; it is {}", rect1.width);
}
}
ここでは、インスタンスの width フィールドの値が 0 より大きければ
width メソッドが true を返し、値が 0 なら false
を返すようにしています。同じ名前のメソッドの中で、そのフィールドを
どのような目的にも使うことができます。main で rect1.width
の後ろに丸括弧を付けると、Rust はそれが width
メソッドを意味すると判断します。丸括弧を付けない場合、Rust はそれが
width フィールドを意味すると判断します。
常にそうとは限りませんが、フィールドと同じ名前をメソッドに付けるとき、 そのメソッドにはフィールドの値を返すだけでほかには何もしないことを 期待する場合がよくあります。このようなメソッドは ゲッター と呼ばれ、 Rust は他のいくつかの言語のように、構造体のフィールドに対してこれを 自動実装しません。ゲッターは、フィールドを非公開にしつつメソッドを 公開にでき、その結果、そのフィールドへの読み取り専用アクセスをその型の 公開 API の一部として提供できるので便利です。公開と非公開とは何か、 またフィールドやメソッドを公開または非公開として指定する方法については、 第7章 で説明します。
->演算子はどこへ行ったのか?C と C++ では、メソッド呼び出しに2種類の演算子を使います。オブジェクト 自体に対して直接メソッドを呼び出す場合は
.を使い、オブジェクトへのポインタに対してメソッドを呼び出し、その前に ポインタをデリファレンスする必要がある場合は->を使います。言い換えると、objectがポインタであるなら、object->something()は(*object).something()に似ています。Rust には
->演算子に相当するものはありません。その代わりに、Rust には 自動参照および自動デリファレンス と呼ばれる機能があります。 メソッド呼び出しは、Rust でこの振る舞いを持つ数少ない箇所の1つです。仕組みは次のとおりです。
object.something()のようにメソッドを呼び出す と、Rust はobjectがそのメソッドのシグネチャに合うように、&、&mut、または*を自動的に補います。言い換えると、次の2つは同じです。#![allow(unused)] fn main() { #[derive(Debug,Copy,Clone)] struct Point { x: f64, y: f64, } impl Point { fn distance(&self, other: &Point) -> f64 { let x_squared = f64::powi(other.x - self.x, 2); let y_squared = f64::powi(other.y - self.y, 2); f64::sqrt(x_squared + y_squared) } } let p1 = Point { x: 0.0, y: 0.0 }; let p2 = Point { x: 5.0, y: 6.5 }; p1.distance(&p2); (&p1).distance(&p2); }1つ目のほうがずっとすっきり見えます。この自動参照の振る舞いが機能する のは、メソッドには明確なレシーバー、つまり
selfの型があるからです。レシーバーとメソッド名が分かれば、Rust はそのメソッドが読み取り(&self)、変更(&mut self)、消費(self) のどれを行うかを明確に判断できます。Rust がメソッドレシーバーに対する借用を暗黙にしていることは、所有権を実際に 扱いやすくしている大きな要因です。
より多くの引数を持つメソッド
Rectangle 構造体に 2 つ目のメソッドを実装して、メソッドの使い方を練習してみましょう。
今回は、Rectangle のあるインスタンスが別の Rectangle のインスタンスを受け取り、
2 つ目の Rectangle が self(最初の Rectangle)の中に完全に収まる場合は
true を返し、そうでない場合は false を返すようにしたいと考えています。
つまり、can_hold メソッドを定義したら、リスト 5-14 に示す
プログラムを書けるようにしたいのです。
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
let rect2 = Rectangle {
width: 10,
height: 40,
};
let rect3 = Rectangle {
width: 60,
height: 45,
};
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}
期待される出力は次のようになります。rect2 の両方の寸法が
rect1 の寸法より小さい一方で、rect3 は rect1 より幅が広いためです。
Can rect1 hold rect2? true
Can rect1 hold rect3? false
メソッドを定義したいことは分かっているので、それは impl Rectangle
ブロックの中に置くことになります。メソッド名は can_hold で、別の
Rectangle への不変借用を引数として取ります。引数の型は、メソッドを
呼び出しているコードを見ると分かります。
rect1.can_hold(&rect2) では &rect2 が渡されており、これは
Rectangle のインスタンスである rect2 への不変借用です。これは理にかなっています。
必要なのは rect2 を読むことだけであり(書き込むなら可変借用が必要になります)、
さらに can_hold メソッドを呼び出したあとでも main が rect2 の所有権を
保持して、再び使えるようにしたいからです。can_hold の戻り値は
ブール値になり、実装では self の幅と高さが、もう一方の
Rectangle の幅と高さよりそれぞれ大きいかどうかを確認します。新しい
can_hold メソッドを、リスト 5-15 に示すように、リスト 5-13 の impl ブロックに
追加してみましょう。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
let rect2 = Rectangle {
width: 10,
height: 40,
};
let rect3 = Rectangle {
width: 60,
height: 45,
};
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}
このコードをリスト 5-14 の main 関数と一緒に実行すると、望んでいた
出力が得られます。メソッドは複数の引数を取ることができ、それらは
self 引数の後にシグネチャへ追加します。そうした引数は、関数の引数と
まったく同じように機能します。
関連関数
impl ブロック内で定義されるすべての関数は 関連関数
と呼ばれます。これは、それらが impl の後に書かれた型に関連付けられているためです。
最初の引数に self を持たない(したがってメソッドではない)
関連関数を定義することもできます。そうした関数は、処理対象となる型の
インスタンスを必要としないからです。すでにこの種の関数を 1 つ使っています。
String 型に定義されている String::from 関数です。
メソッドではない関連関数は、新しい構造体インスタンスを返す
コンストラクタとしてよく使われます。こうした関数はしばしば new と
呼ばれますが、new は特別な名前ではなく、言語に組み込まれているわけでも
ありません。たとえば、square という名前の関連関数を用意して、
1 つの寸法引数を受け取り、それを幅と高さの両方に使うこともできます。そうすれば、
同じ値を 2 回指定しなくても、正方形の Rectangle を簡単に作れます。
ファイル名: src/main.rs
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn square(size: u32) -> Self {
Self {
width: size,
height: size,
}
}
}
fn main() {
let sq = Rectangle::square(3);
}
戻り値の型と関数本体にある Self キーワードは、impl キーワードの後に現れる
型、この場合は Rectangle の別名です。
この関連関数を呼び出すには、構造体名とともに :: 構文を使います。
let sq = Rectangle::square(3); がその一例です。この関数は構造体によって
名前空間化されています。:: 構文は、関連関数と、モジュールによって作られる
名前空間の両方に使われます。モジュールについては 第7章 で説明します。
複数の impl ブロック
各構造体は複数の impl ブロックを持てます。たとえば、リスト
5-15 はリスト 5-16 に示すコードと等価で、そこでは各メソッドが
それぞれ独自の impl ブロックに入っています。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
let rect2 = Rectangle {
width: 10,
height: 40,
};
let rect3 = Rectangle {
width: 60,
height: 45,
};
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}
ここではこれらのメソッドを複数の impl ブロックに分ける理由はありませんが、
これは有効な構文です。複数の impl ブロックが役に立つケースは、
第10章でジェネリックな型とトレイトを扱う際に見ていきます。
まとめ
構造体を使うと、自分のドメインにとって意味のある独自の型を作れます。
構造体を使うことで、互いに関連するデータを結び付けたままにでき、
各要素に名前を付けてコードを明確にできます。impl ブロックでは、
その型に関連付けられた関数を定義でき、メソッドは関連関数の一種で、
構造体のインスタンスが持つ振る舞いを指定できます。
しかし、独自の型を作る方法は構造体だけではありません。Rust の enum 機能に進み、 ツールボックスにもう 1 つ道具を加えましょう。