Charaken 技術系ブログ

技術系に関して学んだことや、気になったことを記事にしていく。

【Rust】がばがばRust独学 - 4. Ownership - 3 References/Borrowing

f:id:charaken:20191223210559p:plain

Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)

doc.rust-lang.org

今回も、Ownershipの概要について学びつつ、気になった点をコード確認した結果を記事にしています。

  • versions
$ rustc --version
rustc 1.40.0 (73528e339 2019-12-16)


References, Borrowing

関数ではOwnerを渡さない方法があります。

下記の関数のように、 & を付けることにより、参照渡しが可能になります。これにより、 calc_length 関数は、 main 関数の s のOwnerを保持しないため、 scalc_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 関数のスコープ内 sdangle 関数終了時にスコープ外となるため、値が削除されます。 そのため、参照を返せなくなります。

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関連が絡んできたり、参照渡し周りやスコープ周りが絡んできて初見では理解するのに時間がかかりました・・・

型に厳しく、自分に優しい世界にしていきたい(錯乱)