Rust には参照 (reference) に関する厳しめのルールがある。あるインスタンス a
の不変参照 &a
または可変参照 &mut a
の生成に関して、次の条件が常に満たされなければならない。
- 不変参照は同時にいくつも存在できるが、可変参照は同時に 1 つしか存在できない。
- 不変参照と可変参照は同時に存在できない。
関数の引数に参照を渡すことを借用 (borrowing) という。
メソッドの第一引数には、メソッドを呼び出したインスタンスの参照 &self
or &mut self
が渡される。これは、C++ では暗黙的に行われるものである。また、引数に参照や可変参照を渡すことも多いだろう。このため、設計を間違えれば、メソッド呼び出しの際にコンパイルエラーと闘うことになる。基本的には、積極的に関連関数化した方が良いと思う。Rust はモジュール管理がしやすいので、野良の関数も怖くない。
構造体のネストに関する借用ルールを忘備のためにまとめた。
次のような構造体のネストを考える。各子構造体には、セッターとゲッターを実装する。
// 親構造体
#[derive(Debug)]
struct Fruit {
color: Color, // 子構造体
taste: Taste, // 子構造体
}
#[derive(Debug)]
struct Color {
r: u32,
g: u32,
b: u32,
}
#[derive(Debug)]
struct Taste {
sugar: u32,
salt: u32,
spice: u32,
}
impl Color {
fn set(&mut self, r: u32, g: u32, b: u32) {
self.r = r;
self.g = g;
self.b = b;
}
fn get(&self) -> (u32, u32, u32) {
(self.r, self.g, self.b)
}
}
impl Taste {
fn set(&mut self, sugar: u32, salt: u32, spice: u32) {
self.sugar = sugar;
self.salt = salt;
self.spice = spice;
}
fn get(&self) -> (u32, u32, u32) {
(self.sugar, self.salt, self.spice)
}
}
まず、次のコードを実行する。
fn main() {
let mut apple = Fruit{
color: Color{r: 255, g: 0, b: 0},
taste: Taste{sugar: 10, salt: 5, spice: 0},
};
apple.taste.set(0, 0, 0);
println!("{:?}", apple.taste); // Taste { sugar: 0, salt: 0, spice: 0 }
}
このコードは問題なく動作するが、apple
の生成の際に mut
を付け忘れると、次のコンパイルエラーが発生する。
error[E0596]: cannot borrow `apple.taste` as mutable, as `apple` is not declared as mutable
--> src\main.rs:63:5
|
58 | let apple = Fruit{
| ----- help: consider changing this to be mutable: `mut apple`
...
63 | apple.taste.set(0, 0, 0);
| ^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable
For more information about this error, try `rustc --explain E0596`.
apple
が不変変数として宣言されているので、apple.taste
の可変参照を作れないと怒られている。つまり、次のルールが言える。
ルール 1: 子構造体の可変参照を取得するには、親構造体が可変な変数としてインスタンス化されていなければならない。
次のコードを試す。
impl Fruit {
fn double_taste(&mut self) {
let (sugar, salt, spice) = self.taste.get();
self.taste.set(sugar*2, salt*2, spice*2);
}
}
fn main() {
let mut apple = Fruit{
color: Color{r: 255, g: 0, b: 0},
taste: Taste{sugar: 10, salt: 5, spice: 0},
};
apple.double_taste();
println!("{:?}", apple.taste); // Taste { sugar: 20, salt: 10, spice: 0 }
このコードは問題なく動作する。このことから、まず次のルールを確認しておく。
ルール 2: 関数内において、引数として借用した可変参照から、その子構造体の (不変/可変) 参照を取得できる。取得後も親構造体の参照は使用できる。
もっというと、借用した可変参照については、同じ関数内で自身の参照も取得できる。ややこしい点としては、親構造体の可変参照と子構造体の参照が野良で共存できるわけではない。このルールは引数として渡された親構造体にのみ、その関数内において適用される。言い換えると、ある関数に仮引数として渡された参照は、その関数スコープ内で上述した参照の共存ルールを考える際にカウントされない。実際に、次のコードはコンパイルエラーになる。
fn main() {
let mut apple = Fruit{
color: Color{r: 255, g: 0, b: 0},
taste: Taste{sugar: 10, salt: 5, spice: 0},
};
let apple_ref = &mut apple;
let taste_ref = &apple.taste;
apple_ref.double_taste();
}
error[E0502]: cannot borrow `apple.taste` as immutable because it is also borrowed as mutable
--> src\main.rs:76:21
|
75 | let apple_ref = &mut apple;
| ---------- mutable borrow occurs here
76 | let taste_ref = &apple.taste;
| ^^^^^^^^^^^^ immutable borrow occurs here
77 | apple_ref.double_taste();
| ------------------------ mutable borrow later used here
For more information about this error, try `rustc --explain E0502`.
可変参照と不変参照の共存ができないとして怒られている。
上述の <コード3>
において、メソッド double_taste()
の仮引数を &self
に変更すると、次のエラーが吐かれる。
error[E0596]: cannot borrow `self.taste` as mutable, as it is behind a `&` reference
--> src\main.rs:46:9
|
44 | fn double_taste(&self) {
| ----- help: consider changing this to be a mutable reference: `&mut self`
45 | let (sugar, salt, spice) = self.taste.get();
46 | self.taste.set(sugar*2, salt*2, spice*2);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable
double_taste()
を呼んだインスタンスが &self
として借用されているので、self.taste
の可変参照は生成できない。つまり、次のルールがある。これは、不変参照を外して可変参照を取得できないのと同類のルールだと思う。
ルール 3: 親構造体の不変参照からは、子構造体の可変参照を取得できない。
最後に、次のコードを試す。
impl Fruit {
fn swap(&mut self) {
let color_ref = &mut self.color;
let taste_ref = &mut self.taste;
let (r, g, b) = taste_ref.get();
let (sugar, salt, spice) = color_ref.get();
taste_ref.set(sugar, salt, spice);
color_ref.set(r, g, b);
}
}
fn main() {
let mut apple = Fruit{
color: Color{r: 255, g: 0, b: 0},
taste: Taste{sugar: 10, salt: 5, spice: 0},
};
apple.swap();
println!("{:?}", apple.taste); // Taste { sugar: 255, salt: 0, spice: 0 }
println!("{:?}", apple.color); // Color { r: 10, g: 5, b: 0 }
}
このコードは問題なく動作する。メソッド内では、2 つの子構造体の可変参照を取得した。つまり、次のルールがある。
ルール 4: 複数の子構造体の可変参照は、互いに共存できる。
子構造体のうちの 1 つの可変参照を取得したことは、他の子構造体の参照を取得する際には気にしなくてよい。