メソッド記法
メソッドは関数に似ています: fnキーワードと名前を使って宣言され、引数と戻り値を持つことができ、
メソッドがどこか別の場所で呼び出された時に実行されるコードを含みます。
関数とは異なり、メソッドは構造体の文脈の中で定義されます(enumの文脈やトレイトオブジェクトの文脈の中でも定義されますが、これらについてはそれぞれ6章と17章で解説します)。
最初の引数は必ずselfになり、これはメソッドが呼び出されている構造体インスタンスを表します。
メソッドを定義する
Rectangleインスタンスを引数に取るarea関数を変え、代わりにRectangle構造体上にareaメソッドを作りましょう。
リスト5-13に示した通りですね。
ファイル名: src/main.rs
#[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() ); }
リスト5-13: Rectangle構造体上にareaメソッドを定義する
Rectangleの文脈内で関数を定義するには、Rectangleのためのimpl(implementation; 実装)ブロックを始めます。
このimplブロック内のあらゆる定義はRectangleに関連付けられたものとなるでしょう。
それからarea関数をimplの波かっこ内に移動させ、最初の(今回は唯一の)引数をシグニチャ内と本体内全てでselfに変えます。
area関数を呼び出し、rect1を引数として渡すmainでは、代替としてメソッド記法を使用して、
Rectangleインスタンスのareaメソッドを呼び出せます。メソッド記法は、インスタンスの後に続きます:
ドット、メソッド名、かっこ、そして引数と続くわけです。
areaのシグニチャでは、rectangle: &Rectangleの代わりに&selfを使用しています。
&selfは実際のところself: &Selfの省略記法です。
implブロック内では、型Selfはそのimplブロックの対象である型に対するエイリアスとして使えます。
メソッドはその第1引数としてselfという名前のSelf型引数を持たなくてはならないので、
Rust処理系は第1引数の位置ではselfという名前のみに省略することを許可しているのです。
ただし、self省略記法の前の&は依然として必要であることに注意してください。
これは、rectangle: &Rectangleと書いたときと同様に、このメソッドがSelfのインスタンスを借用することを示しています。
メソッドは、selfの所有権を奪ったり、ここでしているように不変でselfを借用したり、可変でselfを借用したりできるのです。
他の引数と全く同じですね。
ここで&selfを選んでいるのは、関数バージョンで&Rectangleを使用していたのと同様の理由です:
所有権はいらず、構造体のデータを読み込みたいだけで、書き込む必要はないわけです。
メソッドの一部でメソッドを呼び出したインスタンスを変更したかったら、第1引数に&mut selfを使用するでしょう。
selfだけを第1引数にしてインスタンスの所有権を奪うメソッドを定義することは稀です; このテクニックは通常、
メソッドがselfを何か別のものに変形し、変形後に呼び出し元が元のインスタンスを使用できないようにしたい場合に使用されます。
関数の代替としてメソッドを使う主な理由は、メソッド記法を使用できるようにすることと、
すべてのメソッドのシグニチャでいちいちselfの型を繰り返す必要がなくなることに加えて、体系化のためです。
コードの将来的な利用者に、私たちが提供するライブラリ内のあらゆる箇所からRectangleの機能を探させるのではなく、
この型のインスタンスでできることを一つのimplブロックにまとめあげています。
メソッドには、構造体のフィールドと同じ名前を与えることもできることに注意してください。
例えば、Rectangleにwidthという名前のメソッドを定義することができます:
ファイル名: src/main.rs
#[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メソッドは、インスタンスのwidthフィールドの値が0より大きい場合にtrueを返し、値が0の場合はfalseを返すようにしています:
同名のメソッド内でも、フィールドをあらゆる目的のために使用することができます。
mainではrect1.widthの後に丸かっこを続けているので、処理系はメソッドのwidthを意図したものと認識します。
丸かっこを使わなければ、処理系はフィールドのwidthを意図したものと認識します。
フィールドの値を返すだけで他に何もしないメソッドに、フィールドと同じ名前を与えることが、常にではありませんが、よくあります。 このようなメソッドはゲッターと呼ばれます。 他のいくつかの言語で行われているような自動的なゲッターの実装を、Rustが構造体フィールドに対して行うことはありません。 フィールドを非公開にしながらメソッドは公開するということができ、そうすることで、型の公開APIの一部としてフィールドへの読み込み専用アクセスを提供できるので、ゲッターは有用です。 公開と非公開とはなにか、そしてフィールドやメソッドを公開または非公開として指定する方法については、7章で議論します。
->演算子はどこに行ったの?CとC++では、メソッド呼び出しには2種類の異なる演算子が使用されます: オブジェクトに対して直接メソッドを呼び出すのなら、
.を使用するし、オブジェクトのポインタに対してメソッドを呼び出し、 先にポインタを参照外しする必要があるなら、->を使用するわけです。 言い換えると、objectがポインタなら、object->something()は、(*object).something()と同等なのです。Rustには
->演算子の代わりとなるようなものはありません; その代わり、Rustには、 自動参照および参照外しという機能があります。Rustにおいてメソッド呼び出しは、 この動作が行われる数少ない箇所なのです。動作方法はこうです:
object.something()とメソッドを呼び出すと、 コンパイラはobjectがメソッドのシグニチャと合致するように、自動で&か&mut、*を付与するのです。 要するに、以下のコードは同じものです:#![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); }前者の方がずっと明確です。メソッドには自明な受け手(
selfの型)がいるので、この自動参照機能は動作するのです。 受け手とメソッド名が与えられれば、コンパイラは確実にメソッドが読み込み専用(&self)か、書き込みもする(&mut self)のか、 所有権を奪う(self)のか判断できるわけです。メソッドの受け手に関して借用が明示されないというのが、 所有権を実際に使うのがRustにおいて簡単である大きな理由です。
より引数の多いメソッド
Rectangle構造体に2番目のメソッドを実装して、メソッドを使う練習をしましょう。
今回はRectangleのインスタンスに別のRectangleのインスタンスを受け取らせ、2番目のRectangleがself(1番目のRectangle)に完全に収まるなら、trueを返すようにしたいとしましょう;
そうでなければ、falseを返すべきです。つまり、一旦can_holdメソッドを定義したら、リスト5-14のようなプログラムを書けるようにしたいのです。
ファイル名: src/main.rs
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: まだ書いていないcan_holdメソッドを使用する
予期される出力は以下のようになります。なぜなら、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メソッドを呼び出した後にもrect2が使えるよう、所有権をmainに残したままにしたいからです。
can_holdの返り値は、booleanになり、メソッドの中身は、selfの幅と高さがもう一つのRectangleの幅と高さよりも、
それぞれ大きいことを確認します。リスト5-13のimplブロックに新しいcan_holdメソッドを追記しましょう。
リスト5-15に示した通りです。
ファイル名: src/main.rs
#[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-15: 別のRectangleのインスタンスを引数として取るcan_holdメソッドを、
Rectangleに実装する
このコードをリスト5-14のmain関数と合わせて実行すると、望み通りの出力が得られます。
メソッドは、self引数の後にシグニチャに追加した引数を複数取ることができ、
その引数は、関数の引数と同様に動作するのです。
関連関数
implブロック内で定義されたすべての関数は、implの後に書かれた型に関連付けられているので、関連関数と呼ばれます。
対象の型のインスタンスを必要としないために、selfを第1引数として持たない(つまりメソッドではない)関連関数を定義することもできます。
このような関数は、すでにひとつ使用しています: String::from関数はString型の上に定義された関数です。
メソッドでない関連関数は、構造体の新規インスタンスを返すコンストラクタによく使用されます。
これらにはしばしばnewという名前が付けられますが、newは特別な名前ではなく、言語に組み込まれたものでもありません。
例えば、一つの寸法を引数に取り、それを高さと幅の両方として使用する関連関数squareを提供してもよいでしょう。
そうすれば同じ値を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に示したコードと等価で、
リスト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)); }
リスト5-16: 複数のimplブロックを使用してリスト5-15を書き直す
ここでこれらのメソッドを個々のimplブロックに分ける理由はないのですが、合法な書き方です。
複数のimplブロックが有用になるケースは第10章で見ますが、そこではジェネリック型と、トレイトについて議論します。
まとめ
構造体により、ドメインにとって意味のある独自の型を作成することができます。構造体を使用することで、
関連のあるデータ片を相互に結合させたままにし、各部品に名前を付け、コードを明確にすることができます。
implブロック内では型に関連付けられた関数を定義することができ、メソッドは構造体のインスタンスが持つ振る舞いを規定することができる関連関数の一種です。
しかし、構造体だけが独自の型を作成する手段ではありません: Rustのenum機能に目を向けて、 別の道具を道具箱に追加しましょう。