はじめに
メソッド構文へようこそ。この実験は、Rust Bookの一部です。LabEx で Rust のスキルを練習できます。
この実験では、メソッドはfn
キーワードと名前で宣言され、パラメータと戻り値を持つことができ、構造体のコンテキスト内で定義されます。最初のパラメータは常にself
で、呼び出されている構造体のインスタンスを表します。
This tutorial is from open-source community. Access the source code
💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください
メソッド構文へようこそ。この実験は、Rust Bookの一部です。LabEx で Rust のスキルを練習できます。
この実験では、メソッドはfn
キーワードと名前で宣言され、パラメータと戻り値を持つことができ、構造体のコンテキスト内で定義されます。最初のパラメータは常にself
で、呼び出されている構造体のインスタンスを表します。
メソッドは関数に似ています。fn
キーワードと名前で宣言し、パラメータと戻り値を持ち、他の場所からメソッドが呼び出されたときに実行されるコードを含んでいます。関数とは異なり、メソッドは構造体(または列挙体またはトレイトオブジェクト、それぞれ第 6 章と第 17 章で扱います)のコンテキスト内で定義され、最初のパラメータは常にself
で、メソッドが呼び出されている構造体のインスタンスを表します。
Rectangle
インスタンスをパラメータとして持つarea
関数を変更し、代わりにRectangle
構造体に定義されたarea
メソッドにしましょう。リスト 5-13 に示すようになります。
ファイル名:src/main.rs
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
1 impl Rectangle {
2 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.",
3 rect1.area()
);
}
リスト 5-13: Rectangle
構造体にarea
メソッドを定義する
Rectangle
のコンテキスト内で関数を定義するには、Rectangle
用のimpl
(実装)ブロックを開始します[1]。このimpl
ブロック内のすべてのものはRectangle
型に関連付けられます。次に、area
関数をimpl
の波括弧内に移動し[2]、シグネチャと本体のどこでも最初の(この場合は唯一の)パラメータをself
に変更します。main
では、area
関数を呼び出してrect1
を引数として渡していましたが、代わりにメソッド構文を使用してRectangle
インスタンスのarea
メソッドを呼び出すことができます[3]。メソッド構文はインスタンスの後に続きます。ドットを追加し、その後にメソッド名、丸括弧、および任意の引数を追加します。
area
のシグネチャでは、&self
を使用してrectangle: &Rectangle
の代わりにします。&self
は実際にはself: &Self
の省略形です。impl
ブロック内では、型Self
はimpl
ブロックが対象とする型のエイリアスです。メソッドは最初のパラメータとしてSelf
型のself
という名前のパラメータを持たなければなりません。そのため、Rust は最初のパラメータの場所でこれをself
という名前だけで省略することができます。ただし、self
の省略形の前に&
を使用する必要があります。これは、このメソッドがSelf
インスタンスを借用することを示すためで、rectangle: &Rectangle
の場合と同じです。メソッドはself
の所有権を取得することができ、ここで行ったようにself
を不変に借用することも、self
を可変に借用することもできます。他のパラメータと同じようにです。
ここで&self
を選んだ理由は、関数バージョンで&Rectangle
を使用した理由と同じです。所有権を取得したくなく、構造体のデータを読み取るだけで書き込みたくないからです。メソッドの一部としてメソッドを呼び出したインスタンスを変更したい場合、最初のパラメータとして&mut self
を使用します。最初のパラメータとして単にself
を使用してインスタンスの所有権を取得するメソッドはまれです。この技術は、通常、メソッドがself
を別のものに変換し、変換後に呼び出し元が元のインスタンスを使用できなくなるようにするために使用されます。
関数の代わりにメソッドを使用する主な理由は、メソッド構文を提供し、各メソッドのシグネチャでself
の型を繰り返さなくて済むことに加えて、組織化のためです。型のインスタンスでできることすべてを 1 つのimpl
ブロックにまとめており、提供するライブラリのさまざまな場所でRectangle
の機能を探す必要がなくなります。
構造体のフィールドと同じ名前のメソッドを与えることができることに注意してください。たとえば、Rectangle
にもwidth
という名前のメソッドを定義できます。
ファイル名:src/main.rs
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 は自動的に&
、&mut
、または*
を追加して、object
がメソッドのシグネチャに一致するようにします。言い換えると、次の 2 つは同じです。p1.distance(&p2); (&p1).distance(&p2);
最初の方がはるかにクリーンに見えます。この自動参照動作が機能するのは、メソッドには明確なレシーバー(
self
の型)があるからです。メソッドのレシーバーと名前が与えられると、Rust はメソッドが読み取り(&self
)、変更(&mut self
)、または消費(self
)しているかどうかを確定的に判断できます。Rust がメソッドレシーバーに対する借用を暗黙的に行うことは、所有権を実践的に使いやすくするための大きな要素の 1 つです。
Rectangle
構造体に 2 番目のメソッドを実装することで、メソッドの使用方法を練習しましょう。今回は、Rectangle
のインスタンスに別のRectangle
のインスタンスを渡し、2 番目のRectangle
がself
(最初の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
を渡しています。これはrect2
(Rectangle
のインスタンス)への不変借用です。これは理にかなっています。なぜなら、rect2
を読み取るだけで済む(書き込む場合は可変借用が必要になります)ため、main
がrect2
の所有権を保持して、can_hold
メソッドを呼び出した後も再利用できるようにするためです。can_hold
の戻り値はブール値になり、実装ではself
の幅と高さがそれぞれ他のRectangle
の幅と高さより大きいかどうかを確認します。リスト 5-13 のimpl
ブロックに新しいcan_hold
メソッドを追加しましょう。リスト 5-15 に示します。
ファイル名:src/main.rs
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-15: 別のRectangle
インスタンスをパラメータとして持つRectangle
にcan_hold
メソッドを実装する
リスト 5-14 のmain
関数でこのコードを実行すると、期待される出力が得られます。メソッドはself
パラメータの後にシグネチャに追加する複数のパラメータを持つことができ、それらのパラメータは関数のパラメータと同じように機能します。
impl
ブロック内で定義されるすべての関数は、impl
の後に名前付けされた型と関連付けられているため、関連付けられた関数と呼ばれます。self
を最初のパラメータとして持たない(したがってメソッドではない)関連付けられた関数を定義することができます。なぜなら、それらは型のインスタンスを使用して動作する必要がないからです。このような関数を既に 1 つ使用しています。String
型に定義されたString::from
関数です。
メソッドではない関連付けられた関数は、構造体の新しいインスタンスを返すコンストラクタによく使用されます。これらはしばしばnew
と呼ばれますが、new
は特別な名前ではなく、言語に組み込まれていません。たとえば、1 つの寸法パラメータを持ち、それを幅と高さの両方として使用することで、正方形のRectangle
を作成するのが簡単になるように、square
という名前の関連付けられた関数を提供することができます。そうすることで、同じ値を 2 回指定する必要がなくなります。
ファイル名:src/main.rs
impl Rectangle {
fn square(size: u32) -> 1 Self {
2 Self {
width: size,
height: size,
}
}
}
戻り値の型[1]と関数の本体[2]にあるSelf
キーワードは、impl
キーワードの後に現れる型のエイリアスです。この場合、それはRectangle
です。
この関連付けられた関数を呼び出すには、構造体名とともに::
構文を使用します。let sq = Rectangle::square(3);
がその例です。この関数は構造体によって名前空間化されます。::
構文は、関連付けられた関数とモジュールによって作成される名前空間の両方に使用されます。第 7 章でモジュールについて説明します。
各構造体は複数のimpl
ブロックを持つことができます。たとえば、リスト 5-15 は、それぞれのメソッドが独自のimpl
ブロックにあるリスト 5-16 に示すコードと同等です。
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
}
}
リスト 5-16: 複数のimpl
ブロックを使用してリスト 5-15 を書き直す
ここでは、これらのメソッドを複数のimpl
ブロックに分ける理由はありませんが、これは有効な構文です。第 10 章でジェネリック型とトレイトについて説明する際に、複数のimpl
ブロックが役に立つケースを見ます。
おめでとうございます!あなたはメソッド構文の実験を完了しました。あなたのスキルを向上させるために、LabEx でさらに実験を行って練習することができます。