<style> /* basic design */ .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6, .reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p { font-family: 'Meiryo UI', 'Source Sans Pro', Helvetica, sans-serif, 'Helvetica Neue', 'Helvetica', 'Arial', 'Hiragino Sans', 'ヒラギノ角ゴシック', YuGothic, 'Yu Gothic'; text-align: left; line-height: 1.6; letter-spacing: normal; text-shadow: none; word-wrap: break-word; color: #444; } .reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6 {font-weight: bold;} .reveal h1, .reveal h2, .reveal h3 {color: #2980b9;} .reveal th {background: #DDD;} .reveal section img {background:none; border:none; box-shadow:none; max-width: 95%; max-height: 95%;} .reveal blockquote {width: 90%; padding: 0.5vw 3.0vw;} .reveal table {margin: 1.0vw auto;} .reveal code {line-height: 1.2;} .reveal p, .reveal li {padding: 0vw; margin: 0vw;} .reveal .box {margin: -0.5vw 1.5vw 2.0vw -1.5vw; padding: 0.5vw 1.5vw 0.5vw 1.5vw; background: #EEE; border-radius: 1.5vw;} /* table design */ .reveal table {background: #f5f5f5;} .reveal th {background: #444; color: #fff;} .reveal td {position: relative; transition: all 300ms;} .reveal tbody:hover td { color: transparent; text-shadow: 0 0 3px #aaa;} .reveal tbody:hover tr:hover td {color: #444; text-shadow: 0 1px 0 #fff;} /* blockquote design */ .reveal blockquote { width: 90%; padding: 0.5vw 0 0.5vw 6.0vw; font-style: italic; background: #f5f5f5; } .reveal blockquote:before{ position: absolute; top: 0.1vw; left: 1vw; content: "\f10d"; font-family: FontAwesome; color: #2980b9; font-size: 3.0vw; } /* font size */ .reveal h1 {font-size: 5.0vw;} .reveal h2 {font-size: 4.0vw;} .reveal h3 {font-size: 2.8vw;} .reveal h4 {font-size: 2.6vw;} .reveal h5 {font-size: 2.4vw;} .reveal h6 {font-size: 2.2vw;} .reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p {font-size: 2.2vw;} .reveal code {font-size: 1.6vw;} /* new color */ .red {color: #EE6557;} .blue {color: #16A6B6;} /* split slide */ #right {left: -18.33%; text-align: left; float: left; width: 50%; z-index: -10;} #left {left: 31.25%; text-align: left; float: left; width: 50%; z-index: -10;} </style> <style> /* specific design */ .reveal h2 { padding: 0 1.5vw; margin: 0.0vw 0 2.0vw -2.0vw; border-left: solid 1.2vw #2980b9; border-bottom: solid 0.8vw #d7d7d7; } </style> <!-- --------------------------------------------------------------------------------------- --> # Rust勉強会 第2回 ## 4章 所有権とは? ### 2020/8/4 原山和之 --- ## 本日のメニュー 1. メモリの話 1. 所有権の話 1. 参照と借用 1. スライス型 --- ## スタックとヒープについて予習 今日の勉強会ではスタックとヒープの理解が必要です。 プログラムの変数や関数のあ使い方についての話ですね。 https://www.ipa.go.jp/security/awareness/vendor/programmingv2/contents/c005.html https://www.ipa.go.jp/security/awareness/vendor/programmingv2/contents/c006.html https://keens.github.io/blog/2017/04/30/memoritosutakkutohi_puto/ --- ## メモリ1 * メモリは配列 * 2^64 byteの配列のうち使いたい文だけ占有します。 * メモリの中 * text領域: プログラムを置く * data領域: 初期化されたグローバル変数を置く * bss領域: 初期化されていない(データ領域だけ確保された)グローバル変数を置く * stack領域: 関数の引数やローカル変数を置く * heap領域: プログラムのデータを置く --- ## メモリ2 * text、 data、 bssは実行する前からサイズが分かっている * stackは高速 * 全て既知の固定サイズでなければならない * スタック上のデータは全て既知の固定サイズでなければならない * heapは遅い * コンパイル時にサイズがわからなかったり、サイズが可変のデータ --- ## 所有権規則 所有権のルール: * Rustの各値は、所有者と呼ばれる変数と対応している。 * いかなる時も所有者は一つである。 * 所有者がスコープから外れたら、値は破棄される --- ## 変数スコープ スコープとは、 要素が有効になるプログラム内の範囲のことです。 ```rust fn main() { // sは、ここでは有効ではない。まだ宣言されていない { let s = "hello";// sは、ここから有効になる // sで作業をする } } // このスコープは終わり。もうsは有効ではない ``` 言い換えると、ここまでに重要な点は二つあります: * sがスコープに入ると、有効になる * スコープを抜けるまで、有効なまま --- ## String型 ヒープに確保されるデータ型 この型はヒープにメモリを確保するので、 コンパイル時にはサイズが不明なテキストも保持することができるのです。 文字列リテラルからString型を生成 ```rust let s = String::from("hello"); ``` 可変化することができます ```rust let mut s = String::from("hello"); s.push_str(", world!"); // push_str()関数は、リテラルをStringに付け加える println!("{}", s); // これは`hello, world!`と出力する ``` --- ## メモリと確保 文字列リテラルはコンパイル時に判明しているので、テキストは最終的なバイナリファイルに直接ハードコードされます。 String型では、可変かつ伸長可能なテキスト破片をサポートするために、コンパイル時には不明な量のメモリを ヒープに確保して内容を保持します。 * メモリは、実行時にOSに要求される。 * String型を使用し終わったら、OSにこのメモリを返還する方法が必要である。 Rustは、異なる道を歩んでいます: メモリを所有している変数がスコープを抜けたら、 メモリは自動的に返還されます。: ```rust { let s = String::from("hello"); // sはここから有効になる // sで作業をする } // このスコープはここでおしまい。sは // もう有効ではない ``` --- ## 変数とデータの相互作用法: ムーブ1 「値5をxに束縛する; それからxの値をコピーしてyに束縛する。」 ``` let x = 5; let y = x; ``` 両方、値は5 整数は既知の固定サイズの単純な値で、これら二つの5という値は、スタックに積まれる --- ## 変数とデータの相互作用法: ムーブ2 エラーから見てみる ```rust let s1 = String::from("hello"); let s2 = s1; println!("{}, world!", s1); ``` https://doc.rust-jp.rs/book/second-edition/ch04-01-what-is-ownership.html#a%E5%A4%89%E6%95%B0%E3%81%A8%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E7%9B%B8%E4%BA%92%E4%BD%9C%E7%94%A8%E6%B3%95-%E3%83%A0%E3%83%BC%E3%83%96 --- ## 変数とデータの相互作用法: クローン "shallow copy"と"deep copy"聞いたことある? スタック上のデータだけでなく、本当にString型のヒープデータのdeep copyが必要ならば、 cloneと呼ばれるよくあるメソッドを使うことができます。 ```rust let s1 = String::from("hello"); let s2 = s1.clone(); println!("s1 = {}, s2 = {}", s1, s2); ``` --- ## スタックのみのデータ:コピー shallow copyになります ```rust let x = 5; let y = x; println!("x = {}, y = {}", x, y); ``` --- ### 所有権と関数 関数に値を渡すことと、値を変数に代入することは似ています。 関数に変数を渡すと、 代入のようにムーブやコピーされます。 ```rust fn main() { let s = String::from("hello"); // sがスコープに入る takes_ownership(s); // sの値が関数にムーブされ... // ... ここではもう有効ではない let x = 5; // xがスコープに入る makes_copy(x); // xも関数にムーブされるが、 // i32はCopyなので、この後にxを使っても // 大丈夫 } // ここでxがスコープを抜け、sもスコープを抜ける。ただし、sの値はムーブされているので、何も特別なことは起こらない。 // fn takes_ownership(some_string: String) { // some_stringがスコープに入る。 println!("{}", some_string); } // ここでsome_stringがスコープを抜け、`drop`が呼ばれる。後ろ盾してたメモリが解放される。 // fn makes_copy(some_integer: i32) { // some_integerがスコープに入る println!("{}", some_integer); } // ここでsome_integerがスコープを抜ける。何も特別なことはない。 ``` --- ## 戻り値とスコープ1 値を返すことでも、所有権は移動します。 ```rust fn main() { let s1 = gives_ownership(); // gives_ownershipは、戻り値をs1に // ムーブする let s2 = String::from("hello"); // s2がスコープに入る let s3 = takes_and_gives_back(s2); // s2はtakes_and_gives_backにムーブされ // 戻り値もs3にムーブされる } // ここで、s3はスコープを抜け、ドロップされる。s2もスコープを抜けるが、ムーブされているので、 // 何も起きない。s1もスコープを抜け、ドロップされる。 fn gives_ownership() -> String { // gives_ownershipは、戻り値を // 呼び出した関数にムーブする let some_string = String::from("hello"); // some_stringがスコープに入る some_string // some_stringが返され、呼び出し元関数に // ムーブされる } // takes_and_gives_backは、Stringを一つ受け取り、返す。 fn takes_and_gives_back(a_string: String) -> String { // a_stringがスコープに入る。 a_string // a_stringが返され、呼び出し元関数にムーブされる } ``` --- ## 続き 変数の所有権は、毎回同じパターンを辿っています: 別の変数に値を代入すると、ムーブされます。 ヒープにデータを含む変数がスコープを抜けると、データが別の変数に所有されるようムーブされていない限り、 dropにより片付けられるでしょう。 --- ## 戻り値とスコープ2 タプル ```rust fn main() { let s1 = String::from("hello"); let (s2, len) = calculate_length(s1); //'{}'の長さは、{}です println!("The length of '{}' is {}.", s2, len); } fn calculate_length(s: String) -> (String, usize) { let length = s.len(); // len()メソッドは、Stringの長さを返します (s, length) } ``` --- ## 参照と借用 所有権を取り、またその所有権を戻す、ということを全ての関数でしていたら、ちょっとめんどくさいですね。 値の所有権をもらう代わりに引数としてオブジェクトへの参照を取るcalculate_length関数 ```rust 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() } ``` --- ## 可変な参照1 エラーになります ```rust fn main() { let s = String::from("hello"); change(&s); } fn change(some_string: &String) { some_string.push_str(", world"); } ``` --- ## 可変な参照1 正しいやり方 ```rust fn main() { let mut s = String::from("hello"); change(&mut s); } fn change(some_string: &mut String) { some_string.push_str(", world"); } ``` 可変な参照には大きな制約が一つあります: 特定のスコープで、ある特定のデータに対しては、 一つしか可変な参照を持てないことです。 --- ## 可変な参照2 エラーになります ```rust let mut s = String::from("hello"); let r1 = &mut s; let r2 = &mut s; ``` データ競合が起こるコードをコンパイルさえしない --- ## 可変な参照3 複数の可変な参照 ```rust let mut s = String::from("hello"); { let r1 = &mut s; } // r1はここでスコープを抜けるので、問題なく新しい参照を作ることができる let r2 = &mut s; ``` --- ## 可変な参照4 可変と不変な参照を組み合わせることに関しても、似たような規則が存在しています。 ```rust let mut s = String::from("hello"); let r1 = &s; // 問題なし let r2 = &s; // 問題なし let r3 = &mut s; // 大問題! ``` --- ## 宙に浮いた参照 * ポインタのある言語では、誤ってダングリングポインタを生成してしまいやすいです。ダングリングポインタとは、 他人に渡されてしまった可能性のあるメモリを指すポインタのこと * ダングリングポインタ:その箇所へのポインタを保持している間に、 メモリを解放してしまうこと ```rust fn main() { let reference_to_nothing = dangle(); } fn dangle() -> &String { let s = String::from("hello"); &s } ``` --- ## 参照の規則 * 任意のタイミングで、一つの可変参照か不変な参照いくつでものどちらかを行える。 * 参照は常に有効でなければならない。 --- ## スライス型 所有権のない別のデータ型は、スライスです。 文字列を受け取って、その文字列中の最初の単語を返す関数を書いてください。 ```rust fn first_word(s: &String) -> usize { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return i; } } s.len() } ``` --- ## 文字列スライス1 文字列スライスとは、Stringの一部への参照 ```rust let s = String::from("hello world"); let hello = &s[0..5]; let world = &s[6..11]; ``` --- ## 文字列リテラル2 問題を修正すると ```rust fn first_word(s: &String) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] } ``` --- ## 他のスライス ``` let a = [1, 2, 3, 4, 5]; ``` ```rust let a = [1, 2, 3, 4, 5]; let slice = &a[1..3]; ``` --- ## 参考資料 --- # ご清聴ありがとうございました
{"metaMigratedAt":"2023-06-15T11:20:11.544Z","metaMigratedFrom":"YAML","title":"第2回 4章所有権とは?","breaks":true,"description":"Rust勉強会第2回のスライド","slideOptions":"{\"theme\":\"white\",\"slideNumber\":\"c/t\",\"center\":false,\"transition\":\"none\",\"keyboard\":true}","contributors":"[{\"id\":\"ed5d0581-544f-4aa0-a6ad-2f48be3d325d\",\"add\":29678,\"del\":19320}]"}
    311 views