メソッド記法
メソッドは関数に似ています: 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() ); }
Rectangle
の文脈内で関数を定義するには、impl
(implementation; 実装)ブロックを始めます。
それからarea
関数をimpl
の波かっこ内に移動させ、最初の(今回は唯一の)引数をシグニチャ内と本体内全てでself
に変えます。
area
関数を呼び出し、rect1
を引数として渡すmain
では、代替としてメソッド記法を使用して、
Rectangle
インスタンスのarea
メソッドを呼び出せます。メソッド記法は、インスタンスの後に続きます:
ドット、メソッド名、かっこ、そして引数と続くわけです。
area
のシグニチャでは、rectangle: &Rectangle
の代わりに&self
を使用しています。
というのも、コンパイラは、このメソッドがimpl Rectangle
という文脈内に存在するために、
self
の型がRectangle
であると把握しているからです。&Rectangle
と同様に、
self
の直前に&
を使用していることに注意してください。メソッドは、self
の所有権を奪ったり、
ここでしているように不変でself
を借用したり、可変でself
を借用したりできるのです。
他の引数と全く同じですね。
ここで&self
を選んでいるのは、関数バージョンで&Rectangle
を使用していたのと同様の理由です:
所有権はいらず、構造体のデータを読み込みたいだけで、書き込む必要はないわけです。
メソッドの一部でメソッドを呼び出したインスタンスを変更したかったら、第1引数に&mut self
を使用するでしょう。
self
だけを第1引数にしてインスタンスの所有権を奪うメソッドを定義することは稀です; このテクニックは通常、
メソッドがself
を何か別のものに変形し、変形後に呼び出し元が元のインスタンスを使用できないようにしたい場合に使用されます。
関数の代替としてメソッドを使う主な利点は、メソッド記法を使用して全メソッドのシグニチャでself
の型を繰り返す必要がなくなる以外だと、
体系化です。コードの将来的な利用者にRectangle
の機能を提供しているライブラリ内の各所でその機能を探させるのではなく、
この型のインスタンスでできることを一つのimpl
ブロックにまとめあげています。
->
演算子はどこに行ったの?CとC++では、メソッド呼び出しには2種類の異なる演算子が使用されます: オブジェクトに対して直接メソッドを呼び出すのなら、
.
を使用するし、オブジェクトのポインタに対してメソッドを呼び出し、 先にポインタを参照外しする必要があるなら、->
を使用するわけです。 言い換えると、object
がポインタなら、object->something()
は、(*object).something()
と同等なのです。Rustには
->
演算子の代わりとなるようなものはありません; その代わり、Rustには、 自動参照および参照外しという機能があります。Rustにおいてメソッド呼び出しは、 この動作が行われる数少ない箇所なのです。動作方法はこうです:
object.something()
とメソッドを呼び出すと、 コンパイラはobject
がメソッドのシグニチャと合致するように、自動で&
か&mut
、*
を付与するのです。 要するに、以下のコードは同じものです:# #![allow(unused_variables)] #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
に完全にはめ込まれたら、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 };
// rect1にrect2ははまり込む?
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
メソッドを呼び出した後にもrect2
が使えるよう、所有権をmain
に残したままにしたいからです。
can_hold
の返り値は、booleanになり、メソッドの中身は、self
の幅と高さがもう一つのRectangle
の幅と高さよりも、
それぞれ大きいことを確認します。リスト5-13のimpl
ブロックに新しいcan_hold
メソッドを追記しましょう。
リスト5-15に示した通りです。
ファイル名: src/main.rs
# #![allow(unused_variables)] #fn main() { # #[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 } } #}
このコードをリスト5-14のmain
関数と合わせて実行すると、望み通りの出力が得られます。
メソッドは、self
引数の後にシグニチャに追加した引数を複数取ることができ、
その引数は、関数の引数と同様に動作するのです。
関連関数
impl
ブロックの別の有益な機能は、impl
ブロック内にself
を引数に取らない関数を定義できることです。
これは、構造体に関連付けられているので、関連関数と呼ばれます。それでも、関連関数は関数であり、メソッドではありません。
というのも、対象となる構造体のインスタンスが存在しないからです。もうString::from
という関連関数を使用したことがありますね。
関連関数は、構造体の新規インスタンスを返すコンストラクタによく使用されます。例えば、一次元の引数を取り、
長さと幅両方に使用する関連関数を提供することができ、その結果、同じ値を2回指定する必要なく、
正方形のRectangle
を生成しやすくすることができます。
ファイル名: src/main.rs
# #![allow(unused_variables)] #fn main() { # #[derive(Debug)] # struct Rectangle { # width: u32, # height: u32, # } # impl Rectangle { fn square(size: u32) -> Rectangle { Rectangle { width: size, height: size } } } #}
この関連関数を呼び出すために、構造体名と一緒に::
記法を使用します; 一例はlet sq = Rectangle::square(3);
です。
この関数は、構造体によって名前空間分けされています: ::
という記法は、関連関数とモジュールによって作り出される名前空間両方に使用されます。
モジュールについては第7章で議論します。
複数のimpl
ブロック
各構造体には、複数のimpl
ブロックを存在させることができます。例えば、リスト5-15はリスト5-16に示したコードと等価で、
リスト5-16では、各メソッドごとにimpl
ブロックを用意しています。
# #![allow(unused_variables)] #fn main() { # #[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 } } #}
ここでこれらのメソッドを個々のimpl
ブロックに分ける理由はないのですが、合法な書き方です。
複数のimpl
ブロックが有用になるケースは第10章で見ますが、そこではジェネリック型と、トレイトについて議論します。
まとめ
構造体により、自分の領域で意味のある独自の型を作成することができます。構造体を使用することで、 関連のあるデータ片を相互に結合させたままにし、各部品に名前を付け、コードを明確にすることができます。 メソッドにより、構造体のインスタンスが行う動作を指定することができ、関連関数により、 構造体に特有の機能をインスタンスを利用することなく、名前空間分けすることができます。
しかし、構造体だけが独自の型を作成する手段ではありません: Rustのenum機能に目を向けて、 別の道具を道具箱に追加しましょう。