LoginSignup
0
0

The Rust Programming Languageを読み進める(4章)

Posted at

はじめに

The Rust Programming Languageを頭から読み進めてみる。読みながら要点をまとめていった記事です。

4章:所有権を理解する

4.1章:所有権とは?

スタックとヒープ

  • スタック
    • 後入れ先出しのため、データの場所を考える必要がない
    • 高速だが、サイズは既知である必要がある
  • ヒープ
    • 配置場所に規則はなく、空いている場所を探す
    • スタックより低速だが、サイズは可変で良い

所有権規則

  • Rustにおける所有権とは、メモリを安全に管理するための仕組み
    • Rustの値は、保有者と呼ばれる変数と対応
    • いかなる時も所有者は1つ
    • 所有者がスコープから外れたら値は破棄
      • drop関数が呼ばれ、メモリが解放

所有権のムーブ

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;
    println!("The Value s2 is: {}", s2);
    
    // Error
    // println!("The Value s1 is: {}", s1); 
} 
The Value s2 is: hello
  • s1を定義すると、文字列の中身を保持するメモリへのポインタ、長さ、および許容量がスタックに保持される。そして、ポインタの先にある"hello"は、ヒープ上のメモリに保持される
  • s2 = s1で代入されると、データがコピーされるが、ポインタが指すヒープ上のデータはコピーしない
  • s1, s2両方が生存していると、2回メモリ解放を繰り返すことになるので、s2 = s1の時点で所有権がムーブされる(以下画像の状態)
  • 所有権のムーブ後はs2だけが有効のため、スコープを抜けるとs2だがメモリが開放される
  • Rustはデータのdeep copyは設計上無いとのこと


The Rust Programming Language 日本語版 図4-3から引用

クローン

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();
    
    println!("s1 = {}, s2 = {}", s1, s2);
}
s1 = hello, s2 = hello
  • ヒープデータのコピーにはcloneを使用
  • コピーされたs2は、s1の所有権を持たないためprintln!可能

スタックで保持されるデータ

fn main() {
    let x = 5;
    let y = x;
    
    println!("The Value y is: {}", y);
    
    // NOT error    
    println!("The Value x is: {}", x); 
} 
The Value y is: 5
The Value x is: 5
  • Stringにおける例と矛盾しているように見えるが、整数のような既知のサイズを持つ型はスタック上に保持される
  • なので、y = xのようにコピーするときは所有権の移動が発生しない
  • 値がコピーが起こることをコピーセマンティクスと呼び、これが起きる型はCopyトレイトが実装されている。u32bool, f64, charなどが対象(Rustドキュメント)。

所有権と関数

fn main() {
    let s = String::from("hello");  // sがスコープに入る
    takes_ownership(s);             // sが関数にムーブ
    
    // この時点でsは有効ではない
    // println!("The value of s is: {}", s);

    let x = 5;     // xがスコープに入る
    makes_copy(x); // xが関数にムーブされるがi32はCopy
    println!("The value of x is: {}", x);
} // xもsもスコープを抜ける。ただしsは既にムーブ済み 

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

fn makes_copy(some_integer: i32) {
    println!("{}", some_integer);
} // some_integerがdrop
hello
5
The value of x is: 5
  • 関数に変数を渡すと、ムーブやコピーが発生する
  • ムーブされるStringは、その後に再使用することはできず、Copyされるi32は、再使用が可能

戻り値とスコープ

fn main() {
    let s1 = gives_ownership();        
    println!("{}", s1);                 // hello

    let s2 = String::from("world");     // s2がスコープに入る
    println!("{}", s2);                 // world

    let s3 = takes_and_gives_back(s2);  // s2は関数に所有権がムーブし、戻り値がs3にムーブ
                                        
    // println!("{}", s2);              // s2はムーブしたためNG
    println!("{}", s3);                 // world

} // s1, s3がdrop, s2もスコープ抜けるがムーブ済み

fn gives_ownership() -> String {  
    let some_string = String::from("hello"); // some_stringがスコープに入る
    some_string                              // 呼び出し元関数にムーブされる
}

fn takes_and_gives_back(a_string: String) -> String { 
    a_string  // 呼び出し元関数にムーブされる
}
hello
world
world
  • 全ての関数で所有権を取られ、それをまた返さないといけないのは大変
  • 作業量が多いため、参照という仕組みが存在

4-2章:参照と借用

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);

    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}
The length of 'hello' is 5.
  • &(参照)を渡すことで、関数に所有権を移すこと無く参照できている
  • &s1で参照する参照を生成しているが、所有している訳ではないのでdropされない
  • s.len()も同様に、s自体はスコープを外れるものの、所有権を持っている訳では無いので何も起こらない。なお、引数に参照を取ることを借用と呼ぶ

借りたら返す

fn main() {
    let s = String::from("hello");
    change(&s);
    println!("{}", s);
}

fn change(some_string: &String) {
    some_string.push_str(", world");
}
error[E0596]: cannot borrow immutable borrowed content `*some_string` as mutable
(エラー: 不変な借用をした中身`*some_string`を可変で借用できません)
 --> error.rs:8:5
  |
7 | fn change(some_string: &String) {
  |                        ------- use `&mut String` here to make mutable
8 |     some_string.push_str(", world");
  |     ^^^^^^^^^^^ cannot borrow as mutable
  • 借用した文字列を変更するとどうなるか?エラーが発生する
  • 要するに、借りたものは綺麗に返しましょう

可変な参照

fn main() {
    let mut s = String::from("hello");
    change(&mut s);
    println!("{}", s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}
hello, world
  • 借用を変更するとエラーが発生したものが修正された
  • 違いは可変な参照である&mut sを生成し、&mut Stringで受けること

同時にはできない参照

fn main() {
    let mut s = String::from("hello");

    let r1 = &mut s;
    let r2 = &mut s;

    println!("{}, {}", r1, r2);
}
$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0499]: cannot borrow `s` as mutable more than once at a time
(エラー: 一度に`s`を可変として2回以上借用することはできません)
  • ある特定のデータに対しては、1つしか可変な参照は持てない
  • コンパイラでデータの競合を防いでくれている

可変と不変の参照の組み合わせ

fn main() {
    let mut s = String::from("hello");
    
    let r1 = &s;
    let r2 = &s; 
    let r3 = &mut s; 
}
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as
immutable
(エラー: `s`は不変で借用されているので、可変で借用できません)
  • なぜか自身の環境ではエラーが発生しなかったが、不変な参照をしている間は、可変な参照 (&mut s) はできない
  • 不変な参照している間に、値が変わることを防ぐため

ダングリングポインタ

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");
    &s // sと書くべき
}
error[E0106]: missing lifetime specifier
(エラー: ライフタイム指定子がありません)
  • ダンリングポインタは、ポインタが不正なメモリを指している状態
  • dangle()内で宣言したsは、スコープが終わるところで解放されるが、&sで参照を返そうとしていたため、コンパイラが止めてくれる

4.3章:スライス型

愚直に文字列の長さを数える

fn main() {
    let mut s = String::from("hello world");
    let word = first_word(&s); 

    println!("s: {}", s);
    println!("word: {}", word);

    s.clear(); 
    
    println!("s: {}", s);
    println!("word: {}", word);
}

fn first_word(s: &String) -> usize {
    let bytes = s.as_bytes();
    println!("bytes: {:?}", bytes);

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }

    s.len()
}
bytes: [104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]
s: hello world
word: 5
s: 
word: 5
  • iterはコレクションの各要素を返し、enumerateiterの結果をそのまま返して、タプルの一部として各要素を返す
  • iは添字 (1, 2, 3, ...)、&itemは各要素 (104, 101, 108, ...)
  • s.clear()すると、関連付けられていないwordは値は5のまま維持される

文字列スライス

fn main() {
    let s = String::from("hello world");

    println!("{}", &s[0..5]);
    println!("{}", &s[6..11]);
    println!("{}", &s[..]);
    println!("{}", &s[2..]);
    println!("{}", &s[0..s.len()]);
}
hello
world
hello world
llo world
hello world
  • 形式は[starting_index..ending_index]
  • 末尾の1字を取得したい場合は、&s[s.len()-1..s.len()]でも通るが、&s.chars().last().unwrap()と書けるよう
fn main() {
    let mut s = String::from("hello world");
    let word = first_word(&s); 

    println!("s: {}", &s);
    println!("word: {}", word);

    // Error
    // s.clear(); 
}

fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();
    println!("bytes: {:?}", bytes);

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}
bytes: [104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]
s: hello world
word: hello
  • 文字列スライスを使って、指定した部分まで文字列を出力
  • ただし、s.clear()を実行することはできない
    • ルール1:不変な参照は幾つあってもOK
    • ルール2:可変な参照は1つのみで、他の参照は許可されない
  • words不変借用している中で、clear()を使った可変借用はできない

引数としての文字列スライス

fn main() {
    // 1. String
    let my_string = String::from("hello world");
    let word = first_word(&my_string[..]);
    println!("word: {}", word);

    // 2. &str - スライス記法で指定
    let my_string_literal = "hello world";
    let word = first_word(&my_string_literal[..]);
    println!("word: {}", word);

    // 3. &str - スライス記法なし
    let word = first_word(my_string_literal);
    println!("word: {}", word);
}

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    println!("bytes: {:?}", bytes);

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}
bytes: [104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]
word: hello
bytes: [104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]
word: hello
bytes: [104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]
word: hello
  • 復習として、Stringは変更可能で実態はVec<u8>。一方、&str文字列スライスと呼び、変更不可で実態は&[u8]。文字列リテラルで定義したもの(コメントの2番)は&strとなる
  • 文字列リテラルはコメントの1番のように、全要素を指定して参照を渡すことができるが、3番のように、スライス記法なしでも渡すことが可能

参考にさせていただいたサイト

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0