【Rust】がばがばRust独学 - 4. Ownership - 3 References/Borrowing
Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)
今回も、Ownershipの概要について学びつつ、気になった点をコード確認した結果を記事にしています。
- versions
$ rustc --version rustc 1.40.0 (73528e339 2019-12-16)
References, Borrowing
関数ではOwnerを渡さない方法があります。
下記の関数のように、 &
を付けることにより、参照渡しが可能になります。これにより、 calc_length
関数は、 main
関数の s
のOwnerを保持しないため、
s
が calc_length
関数のスコープ外になっても、値は削除されません。
fn main() { let s = String::from("Hello"); let l = calc_length(&s); println!("s -> {}, l -> {}", s, l); } fn calc_length(s: &String) -> usize { s.len() }
s -> Hello, l -> 5
可変参照
純粋な参照渡しの場合、対象がmutableでなければ、
下記のように、 push_str
を実行することはできません。
fn main() { let s = String::from("Hello"); let l = calc_length(&s); println!("s -> {}, l -> {}", s, l); } fn calc_length(s: &String) -> usize { s.push_str(", Rust!!"); s.len() }
error[E0596]: cannot borrow `*s` as mutable, as it is behind a `&` reference --> demo.rs:9:2 | 8 | fn calc_length(s: &String) -> usize { | ------- help: consider changing this to be a mutable reference: `&mut std::string::String` 9 | s.push_str(", Rust!!"); | ^ `s` is a `&` reference, so the data it refers to cannot be borrowed as mutabl
main
関数内で mut
によるmutableによる宣言をしても、
calc_length
関数側がmutableによる引数定義となっていない場合、
下記の通りエラーとなります。
fn main() { let mut s = String::from("Hello"); let l = calc_length(&s); println!("s -> {}, l -> {}", s, l); } fn calc_length(s: &String) -> usize { s.push_str(", Rust!!"); s.len() }
warning: variable does not need to be mutable --> demo.rs:2:6 | 2 | let mut s = String::from("Hello"); | ----^ | | | help: remove this `mut` | = note: `#[warn(unused_mut)]` on by default error[E0596]: cannot borrow `*s` as mutable, as it is behind a `&` reference --> demo.rs:9:2 | 8 | fn calc_length(s: &String) -> usize { | ------- help: consider changing this to be a mutable reference: `&mut std::string::String` 9 | s.push_str(", Rust!!"); | ^ `s` is a `&` reference, so the data it refers to cannot be borrowed as mutable
そのため、mutableによる参照(可変参照)を実施する場合は、
関数呼び出し時、関数宣言で &mut
をつける必要があります。
fn main() { let mut s = String::from("Hello"); let l = calc_length(&mut s); println!("s -> {}, l -> {}", s, l); } fn calc_length(s: &mut String) -> usize { s.push_str(", Rust!!"); s.len() }
s -> Hello, Rust!!, l -> 13
References and Borrowing - The Rust Programming Language
We can fix the error in the code from Listing 4-6 with just a small tweak
と、少しの変更でできると記載がありますが、少しではない気がします。関数利用時によしなに判断してもらえるとありがたい・・・
fn main() { let mut s = String::from("Hello"); let l = calc_length(&s); println!("s -> {}, l -> {}", s, l); } fn calc_length(s: &mut String) -> usize { s.push_str(", Rust!!"); s.len() }
error[E0308]: mismatched types --> demo.rs:3:22 | 3 | let l = calc_length(&s); | ^^ types differ in mutability | = note: expected type `&mut std::string::String` found type `&std::string::String`
可変参照の特性
特定のスコープ内の特定のデータに対して、可変参照は1つだけもつことができます。
そのため、下記のように、 &mut s
を複数回呼び出す場合は、s2
で2つ目を持たせようとしているため、エラーとなります。
fn main() { let mut s = String::from("Hello"); let s1 = &mut s; let s2 = &mut s; println!("s1-> {}, s2-> {}", s1, s2); }
error[E0499]: cannot borrow `s` as mutable more than once at a time --> demo.rs:5:11 | 4 | let s1 = &mut s; | ------ first mutable borrow occurs here 5 | let s2 = &mut s; | ^^^^^^ second mutable borrow occurs here 6 | 7 | println!("s1-> {}, s2-> {}", s1, s2); | -- first borrow later used here
上記のルールをもたせる利点は、可変参照でのでーた競合を防ぐことができると記載されております。 競合が起きる行動はドキュメント(References and Borrowing - The Rust Programming Language)より、下記の3つとのことでした。
・Two or more pointers access the same data at the same time. ・At least one of the pointers is being used to write to the data. ・There’s no mechanism being used to synchronize access to the data.
Scopeを変えれば問題ないので、
下記のように s2
でのポインタをスコープ内で削除してしまえば、
後続処理でポインタを利用可能になります。
fn main() { let mut s = String::from("Hello"); { let s2 = &mut s; println!("s2-> {}", s2); } let s1 = &mut s; println!("s1-> {}", s1); }
s2-> Hello s1-> Hello
下記のように、スコープ外で一度ポインタが定義されている場合は、s2
では二重定義になるため、エラーとなります。
fn main() { let mut s = String::from("Hello"); let s1 = &mut s; { let s2 = &mut s; println!("s2-> {}", s2); } println!("s1-> {}", s1); }
error[E0499]: cannot borrow `s` as mutable more than once at a time --> demo.rs:6:12 | 4 | let s1 = &mut s; | ------ first mutable borrow occurs here 5 | { 6 | let s2 = &mut s; | ^^^^^^ second mutable borrow occurs here ... 10 | println!("s1-> {}", s1); | -- first borrow later used here
不変参照
下記のようにすると、エラーになります。
fn main() { let mut s = String::from("hello"); let r1 = &s; let r2 = &s; let r3 = &mut s; println!("{}, {}, and {}", r1, r2, r3); }
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable --> demo.rs:6:11 | 4 | let r1 = &s; | -- immutable borrow occurs here 5 | let r2 = &s; 6 | let r3 = &mut s; | ^^^^^^ mutable borrow occurs here 7 | 8 | println!("{}, {}, and {}", r1, r2, r3); | -- immutable borrow later used here
・・・不変参照と可変参照の違いはなんだ!?(混乱) めっちゃ長くなるので、別記事で作成します。 とりあえず、可変参照は後続処理で変更処理をするのであれば、可変参照。 参照渡しのみした場合は不変参照あたりを抑えておけば大丈夫そう。
ぶら下がり参照
下記のように、 dangle
関数のスコープ内 s
は dangle
関数終了時にスコープ外となるため、値が削除されます。
そのため、参照を返せなくなります。
fn main() { let s = dangle(); } fn dangle() -> &String { let s = String::from("hello"); &s }
error[E0106]: missing lifetime specifier --> demo.rs:5:16 | 5 | fn dangle() -> &String { | ^ help: consider giving it a 'static lifetime: `&'static` | = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
fn dangle() -> String
として値を返すべきです。
参照の規則
ドキュメント( References and Borrowing - The Rust Programming Language )より、
・At any given time, you can have either one mutable reference or any number of immutable references. ・References must always be valid.
であるため、
- いつでも、 1つの可変参照または任意の数の不変参照を使用可能
- 参照は常に有効でなければならない
最後に
Cのポインタみたいなんでしょ、楽勝、楽勝!とか思っていたのですが、mutable/immutable関連が絡んできたり、参照渡し周りやスコープ周りが絡んできて初見では理解するのに時間がかかりました・・・
型に厳しく、自分に優しい世界にしていきたい(錯乱)