impl / メソッド定義
| 対応: | Rust 1.0(2015) |
|---|
Rustでは『impl』ブロックを使って構造体にメソッドを追加します。『&self』を受け取るインスタンスメソッドと、selfを受け取らない関連関数(コンストラクタなど)の2種類が定義できます。
構文
struct Rectangle {
width: f64,
height: f64,
}
impl Rectangle {
// 関連関数(コンストラクタ): selfを受け取りません。
fn new(width: f64, height: f64) -> Self {
Rectangle { width, height }
}
// インスタンスメソッド: &self で不変参照を受け取ります。
fn area(&self) -> f64 {
self.width * self.height
}
// &mut self で可変参照を受け取り、フィールドを変更します。
fn scale(&mut self, factor: f64) {
self.width *= factor;
self.height *= factor;
}
}
// 呼び出し方です。
let mut r = Rectangle::new(5.0, 3.0); // 関連関数は :: で呼び出します。
println!("{}", r.area()); // メソッドは . で呼び出します。
r.scale(2.0);
メソッドの種類
| シグネチャ | 概要 |
|---|---|
| fn method(&self) | 不変参照を受け取るインスタンスメソッドです。 |
| fn method(&mut self) | 可変参照を受け取るインスタンスメソッドです(フィールドの変更が可能)。 |
| fn method(self) | 所有権を受け取るメソッドです(呼び出し後にインスタンスは使えません)。 |
| fn func() -> Self | selfを受け取らない関連関数(コンストラクタなどに使います)。 |
| Self | impl 対象の型自身を指す型エイリアスです。型名の代わりに使えます。 |
サンプルコード
『impl』ブロックを使ってstructにメソッドを追加する基本的な使い方を確認するサンプルコードです。
sample_struct_impl_method.rs
#[derive(Debug)]
struct Circle {
x: f64,
y: f64,
radius: f64,
}
impl Circle {
// 関連関数(コンストラクタ)です。
fn new(x: f64, y: f64, radius: f64) -> Self {
Circle { x, y, radius }
}
// 原点に置かれた円を作るファクトリ関数です。
fn at_origin(radius: f64) -> Self {
Circle::new(0.0, 0.0, radius)
}
// &self: 読み取り専用のメソッドです。
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
fn circumference(&self) -> f64 {
2.0 * std::f64::consts::PI * self.radius
}
fn contains(&self, x: f64, y: f64) -> bool {
let dx = self.x - x;
let dy = self.y - y;
(dx * dx + dy * dy).sqrt() <= self.radius
}
// &mut self: フィールドを変更するメソッドです。
fn move_to(&mut self, x: f64, y: f64) {
self.x = x;
self.y = y;
}
fn resize(&mut self, new_radius: f64) {
self.radius = new_radius;
}
}
// impl ブロックは複数に分けても構いません。
impl Circle {
fn description(&self) -> String {
format!(
"Circle at ({}, {}) with radius {}",
self.x, self.y, self.radius
)
}
}
fn main() {
// 関連関数でインスタンスを生成します。
let mut c = Circle::new(0.0, 0.0, 5.0);
println!("{:?}", c);
// メソッドを呼び出します。
println!("面積: {:.2}", c.area());
println!("周囲: {:.2}", c.circumference());
println!("(3,4)を含む?: {}", c.contains(3.0, 4.0)); // true(距離5)
println!("(4,4)を含む?: {}", c.contains(4.0, 4.0)); // false(距離5.65...)
// &mut self メソッドで状態を変更します。
c.move_to(10.0, 20.0);
c.resize(3.0);
println!("{}", c.description());
// 別のコンストラクタです。
let c2 = Circle::at_origin(1.0);
println!("{:?}", c2);
}
コンパイルして実行すると次のように出力されます。
rustc struct_impl_method.rs
./struct_impl_method
Circle { x: 0.0, y: 0.0, radius: 5.0 }
面積: 78.54
周囲: 31.42
(3,4)を含む?: true
(4,4)を含む?: false
Circle at (10, 20) with radius 3
Circle { x: 0.0, y: 0.0, radius: 1.0 }
よくあるミス
よくあるミス1: &selfと&mut selfの使い分けミス
&selfはインスタンスを変更しないメソッドに使い、&mut selfはフィールドを変更するメソッドに使います。変更のないメソッドに&mut selfを使うと呼び出し元でmutが必要になり不便です。
struct_impl_mistake1.rs
struct Counter {
count: u32,
}
impl Counter {
// 読み取りのみなのに&mut selfを使っています。
fn get_count(&mut self) -> u32 { // 不要なmut
self.count
}
}
fn main() {
let c = Counter { count: 5 }; // mutなし
// &mut selfのメソッドを呼ぶにはmutが必要になります。
// c.get_count(); // エラー: cがmutでない
println!("count: 5");
}
rustc struct_impl_mistake1.rs ./struct_impl_mistake1 count: 5
読み取りのみのメソッドは&selfを使います。
struct_impl_fix1.rs
struct Counter {
count: u32,
}
impl Counter {
// 読み取りのみは&selfを使います。
fn get_count(&self) -> u32 {
self.count
}
// 変更するメソッドは&mut selfを使います。
fn increment(&mut self) {
self.count += 1;
}
}
fn main() {
let mut c = Counter { count: 0 };
c.increment();
c.increment();
println!("count: {}", c.get_count()); // 2
// 不変でもget_countは呼べます。
let c2 = Counter { count: 10 };
println!("count: {}", c2.get_count()); // 10
}
rustc struct_impl_fix1.rs ./struct_impl_fix1 count: 2 count: 10
よくあるミス2: selfを消費するメソッドの後にインスタンスを使用してコンパイルエラー
selfを受け取るメソッド(selfが&でも&mutでもない)は呼び出し後にインスタンスが消費され使えなくなります。主にBuilder patternで使われます。
struct_impl_mistake2.rs
struct Builder {
value: i32,
}
impl Builder {
fn new() -> Self { Builder { value: 0 } }
// selfを消費するメソッドです(Builderパターン)。
fn set_value(mut self, v: i32) -> Self {
self.value = v;
self
}
fn build(self) -> i32 { // selfを消費します。
self.value
}
}
fn main() {
let b = Builder::new();
let result = b.build();
// buildがbを消費したので再利用できません。
// b.set_value(5); // コンパイルエラー: use of moved value `b`
println!("result: {}", result);
}
rustc struct_impl_mistake2.rs ./struct_impl_mistake2 result: 0
Builderパターンではメソッドチェーンを使います。
struct_impl_fix2.rs
struct Builder {
value: i32,
label: String,
}
impl Builder {
fn new() -> Self { Builder { value: 0, label: String::new() } }
fn set_value(mut self, v: i32) -> Self { self.value = v; self }
fn set_label(mut self, l: &str) -> Self { self.label = l.to_string(); self }
fn build(self) -> String { format!("{}={}", self.label, self.value) }
}
fn main() {
// メソッドチェーンで使います。
let result = Builder::new()
.set_value(42)
.set_label("score")
.build();
println!("{}", result); // score=42
}
rustc struct_impl_fix2.rs ./struct_impl_fix2 score=42
概要
Rustのメソッドは『impl』ブロック内に定義します。同じ型に対して複数の『impl』ブロックを書くことが可能で、機能ごとに分けて整理できます。
関連関数は型名に『::』を付けて呼び出します(例: 『Circle::new()』)。Rustにはコンストラクタのための特別な構文はなく、慣習として『new()』という名前の関連関数を使います。
構造体の定義は『struct / フィールド定義』を、traitの実装は『trait / impl Trait for 型』を参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。