Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chapter4 所有権を理解する #8

Open
euledge opened this issue Feb 16, 2019 · 8 comments
Open

Chapter4 所有権を理解する #8

euledge opened this issue Feb 16, 2019 · 8 comments

Comments

@euledge
Copy link
Owner

euledge commented Feb 16, 2019

所有権はRustの特徴的な機能、これがあることでガベージコレクタなしで安全性の担保を行うことができる

@euledge
Copy link
Owner Author

euledge commented Feb 16, 2019

4.1 所有権とは

これまでの一般的なプログラミング言語がメモリの管理をする場合以下の2つの方法が用いられてきた。

  1. 明示的に実装者がメモリの確保、開放を行う。
    C言語や、C++言語
  2. ガベージコレクションの機構を持って、定期的に使用されていないメモリを開放する。
    Javaなど

Rustは上記とは異なる、「所有権」と呼ぶ仕組みを採用することで実装者が安全にメモリを使用することができる。

@euledge
Copy link
Owner Author

euledge commented Feb 16, 2019

4.1.2 所有権規則

  • Rustで用いられるメモリ上に確保された値は、所有者と呼ばれる変数と対応している。
  • メモリ上に確保された値の所有者はいかなる時も一つである。
  • 所有者がスコープを外れた時、確保されているメモリは解放される。

@euledge
Copy link
Owner Author

euledge commented Feb 16, 2019

4.1.3 変数スコープ

一般的な他のプログラミング言語と同じ例
文字列リテラルを使用する例(この場合はメモリはスタック上にある)

{
    let s = "Hello!";
    println!("{}", s);  // ここはスコープ内なので有効
}  // ここでスコープを外れるので s は解放される。 
println!("{}", s);  // ここはスコープ外なので参照できない

@euledge
Copy link
Owner Author

euledge commented Feb 16, 2019

4.1.5 メモリの確保

上記のリテラル値の場合は、コンパイル時に必要なメモリの大きさが分かっているが、実行中でないと値が決まらないようなケースの場合はString型を用いて実行時に動的にヒープメモリの確保を行う。

{
    let s = String::from("Hello!");
    println!("{}", s);  // ここはスコープ内なので有効
}  // ここでスコープを外れるので s は解放される。
println!("{}", s);  // ここはスコープ外なので参照できない

String型は、drop()という関数を持っていて、Rustはスコープを外れるときに必ずこの関数を呼ぶ。

@euledge
Copy link
Owner Author

euledge commented Feb 16, 2019

4.1.5.1 変数とデータの相互作用法: ムーブ

下記のように通常のプリミティブな値の場合は、xが持つ '5'という値は
let y = x; とする時その値もコピーされてxもyもスタックに積まれることになるため
x,y 両方とも値を参照でき、表示されスコープを外れる } の箇所で解放される

{
    let x = 5;
    println!("x is: {}!", x);
    let y = x;
    println!("y is: {}!", y);   // 1
    println!("x is: {}!", x);   // 2
} // ここで x,y 両方とも開放
x is: 5!
y is: 5!
x is: 5!

String型を用いた場合、String::fromで確保されたメモリはヒープ上に確保されs1の実態はヒープのメモリを指すポインタのようなものでありそれがスタックに積まれる。

今までのC言語のようなメモリ管理の方法であった場合、この後に let s2 = s1 とした時は ポインタのみがコピーされ、値はヒープ上の同じ場所を指すことになる。
ここで } でスコープを外れた場合、 s1,s2ともに同じヒープ上のメモリを解放しようとして二重メモリ開放エラーを起こしてしまう。

{
   let s1 = String::from("Hello");
   println!("{}!", s1);
}

Rustではこの問題を、 s2 に s1 を代入したときに s1が指すヒープ上のメモリの参照を無効ににしてコンパイル時にエラーとすることで解決している。これをmoveという

{
   let s1 = String::from("Hello");
   println!("{}!", s1);
   let s2 = s1;
   println!("{}!", s2);
   println!("{}!", s1);
}

error[E0382]: borrow of moved value: `s1`
  --> src\main.rs:57:24
   |
55 |        let s2 = s1;
   |                 -- value moved here
56 |        println!("{}!", s2);
57 |        println!("{}!", s1);
   |                        ^^ value borrowed here after move```

これにより、ヒープ上のメモリを参照する変数は唯一となり二重メモリ開放が行われないことになる。

@euledge
Copy link
Owner Author

euledge commented Feb 16, 2019

4.1.5.2 変数とデータの相互作用法: クローン

ヒープ上のメモリの ディープコピーが必要な場合は, clone()を使用する。

{
    let s1 = String::from("Hello");
    println!("{}!", s1);
    let s2 = s1.clone();
    println!("{}!", s2);
    println!("{}!", s1);
}
Hello!
Hello!
Hello!

@euledge
Copy link
Owner Author

euledge commented Feb 19, 2019

4.1.6 所有権と関数

関数の引数に変数を渡すことは、別の変数に代入することと同じなので、上記で説明してきたcopy,moveの考え方に準じる。

fn main() {
    {
        let s = String::from("Hello!");
        takes_ownership(s);
        // 関数の引数に渡すことで所有権が移ってしまう。
        // println!("{}", s);
        let x = 5;

        makes_copy(x);
        // copyされるのでここでも使用できる
        println!("The Value is x: {}", x);
    }
}

fn takes_ownership(some_string: String) {
    println!("{}", some_string);
}

fn makes_copy(number: i32) {
    println!("{}", number);
}

@euledge
Copy link
Owner Author

euledge commented Feb 20, 2019

4.1.7 戻り値とスコープ

上記の例で、関数の引数に変数を渡すことで所有権の移動が起きることが分かったが
その値を呼び出し側で使えるようにするためには呼び出した関数から値を返さなければならない。

fn main() {
    {
        let mut s = String::from("Hello!");
        s = takes_ownership(s);
        println!("{}", s);
    }
}

fn takes_ownership(some_string: String) -> String {
   // 何かの処理
    some_string
}

参照と借用

この記述を毎回行うのは大変煩わしい。
Rustにはこの概念に対する 参照 と呼ばれる機能がある。参照は変数名の頭に & を付加することで表す。

fun main() {
      let s = String::from("Hello!");
      let len = reference_ownership(&s);
      println!("{} length is {}", s, len);
}
fn reference_ownership(some_string: &String) -> usize {
    println!("referenced {}", some_string);
    some_string.len()
}

関数の引数に参照を渡すことを 借用 という。
借用した変数は、変更することはできない。つまり下記の実装は誤りであり、コンパイル時にエラーとなる。

fun main() {
      let s = String::from("Hello!");
      borrow_ownership(&s);
}
fn reference_ownership(some_string: &String)  {
    some_string.push_str(", World!");  
}

参照を可変なものとするためには、以下のような記述をします。

  1. 変数の宣言で mut を付加し可変であることを宣言する。
  2. 関数の引数に &mut で宣言することで可変可能な参照であることを示す。
  3. 関数に変数を渡すときに &mut をつける。
fun main() {
      let mut s = String::from("Hello!");
      borrow_mutable_ownership(&mut s);
      println!("{}", s);
}
fn borrow_mutable_ownership(some_string: &mut String)  {
    some_string.push_str(", World!");
}

可変な参照は、同一スコープ内で同時に使用できないという制限があります

euledge added a commit that referenced this issue Feb 20, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant