###### tags: `Rust` # Rust テキスト学習 2 # 4. シンタックスとセマンティクス 続き ## 所有権(ownership) Rust所有権システム解説その1 - 所有権 <- 今ここ - 借用、参照 - ライフタイム ### 概論 Rustが重視するもの = 安全性&スピード それらを達成するためにたくさんの「ゼロコスト抽象化」を行う ゼロコスト抽象化 = 抽象化機能を小さいコストで実現すること 所有権システムもゼロコスト抽象化の1つ 解析はすべてコンパイル時に行われ、実行時のコストはかからない ユーザーの学習曲線というコストはあるが # 所有権 変数束縛 = リソースの所有権を持つこと 変数のスコープが外れるとき、Rustはその変数に束縛されているリソースを開放する。 ```rust= fn foo() { let v = vec![1, 2, 3]; } ``` 関数が終了して、v が開放されるとき、ヒープに確保されたVec\<T>をも開放する。 vec![T, T, T] = Vec を作るマクロ 末尾に ! がついてるものはマクロの確率が高い ## ムーブセマンティクス ムーブ = 移動 セマンティクス = そのコードがどう動くか つまり、代入分で別の変数に移動しようとしたらどうなるか、という意味 ```rust= let v1 = vec![1,2,3]; let v2 = v1; println!("{} {}", v1[0], v2[0]); ``` これはエラーになる。 `let v2 = v1` : 所有権の移動。ムーブという。 上記の式が意味するものは、 **ヒープ上の[1,2,3]** を参照するVecオブジェクト(ヒープへのポインタ含む)を代入しているということ ポインタのコピーを作る = 同じ内容へのポインタが2つある これはデータ競合を引き起こし、安全性保証への違反になるため Rustはmoveを終えたあとのv1を使用不可にする。 ## Copy トレイト i32, bool, f64 などは、変数束縛を他のものに移動しても 元の変数が利用不可能にならない。 それは、Copyトレイトを実装していてデータのコピーを作成しているから。 Copyトレイとは所有権ルールの挙動を変更することができる。 ## 関数の引数の所有権 もちろん、関数の引数として変数を渡した場合もムーブが生じる。 返り値で変数の所有権を返してもらわなければ、VecやBoxなどは使えなくなる。 これを回避して、変数内でCopyトレイトを持たない型を操作するには 借用(Borrow)という機能を使う # 借用(Borrow) - 所有権 - 借用、参照 <- 今ここ - ライフタイム ## 借用を生かさない厄介なコード ```rust= fn foo(v1: Vec<i32>, v2: Vec<i32>) -> (Vec<i32>, Vec<i32>, i32) { // v1とv2についての作業を行う // 所有権と関数の結果を返す (v1, v2, 42) } let v1 = vec![1, 2, 3]; let v2 = vec![1, 2, 3]; let (v1, v2, answer) = foo(v1, v2); ``` もし、変数の戻り値を受けなかったらv1, v2は二度と使えなくなる。 ## 借用を活かしたコード ```rust= fn foo(v1: &Vec<i32>, v2: &Vec<i32>) -> i32 { // v1とv2についての作業を行う // 答えを返す 42 } let v1 = vec![1, 2, 3]; let v2 = vec![1, 2, 3]; let answer = foo(&v1, &v2); // ここではv1とv2が使える! ``` 借用(Borrow)とは要するに、引数に所有権を渡す代わりに 参照& を渡すこと - & : 参照演算子 - &T: 参照型 参照は、リソースを所有しない。 所有権を借用するだけ。 もし、参照型の変数が定義されたブロックが終了しても、 参照変数が開放されるだけでリソースは開放されない。 また、参照はデフォルトでimutableである。 ## &mut 参照 ```rust= let mut x = 5; { let y = &mut x; *y += 1; } println!("{}", x); ``` 参照したリソースを変更できる。 ## 借用のルール - 借用は所有者よりも長く存続してはならない - imutableな参照(&T)はいくつでも作れる - mutableな参照(&mut T)は同時に1つしか存在できない - &T と &mut T は両立しない ### &T と &mut Tは両立しない ```rust= let mut x = 5; let y = &mut x; *y += 1; println!("{}", x); ``` y に &mut T で借用されているので、println!() でxの&Tの参照を借すことができない ```rust= let mut x = 5; { let y = &mut x; // -+ &mut借用がここから始まる *y += 1; // | } // -+ ... そしてここで終わる println!("{}", x); // <- ここでxを借用しようとする ``` {} のスコープ内で y = &mut x の期限が切れるのでprintln!()でxの&Tを借用できる。 ### メリット: イテレーターの無効化回避 以下のコードは、ループごとに元のVec\<i32>に要素をpushして無限ループにしようとしている。 しかしfor loop によって要素の参照を借用しているので、 元のVecを変更できなくなり、問題を回避する ```rust= let mut v = vec![1, 2, 3]; for i in &v { println!("{}", i); v.push(34); } ``` ### 参照は所有者よりも長く生存してはならない ### メリット: 解放後の使用の制限 ```rust= let y: &i32; { let x = 5; y = &x; } println!("{}", y); ``` リソースの x = 5はスコープが切れて開放される。 しかし、yがxより前に定義されている/xが開放された後も生存していることにより、ルールに抵触してエラーが発生する。 これにより、開放後の不正な領域を参照することがなくなる。 ```rust= let y: &i32; let x = 5; y = &x; println!("{}", y); ``` 上記の場合もエラー。 yの参照を使っている部分はxの生存期間内だが、xを参照する要理も早くyが宣言されている=xより生存期間が長いので エラーになる  # ライフタイム(Lifetime) - 所有権 - 借用、参照 - ライフタイム <- 今ここ ## 複雑なリソース参照の問題 1. リソースを関数に借用させる 2. 関数内でリソースを使い終わり、開放することを決める 3. うっかりして、関数外でまたリソースを使ってしまう いわゆる、無効なリソースの使用である。 これをダングリングポインタ、または「解放後の使用」という 2.のあとに3.が起こらないように、Rustでは **ライフタイム** を使うことで管理する ライフタイム = 参照が有効なスコープの記述 ## ライフタイム 'a : ライフタイムa と呼ぶ 技術的には、参照はすべてライフタイムを持つ。 しかし、一般的にはコンパイラが省略してくれる。 ```rust= // 一般的には'aをつけない関数と等価 fn foo<'a>(x: &'a i32) { } ``` ### fn func<>(...) 関数は 関数名<> の <>の間に、 **ジェネリックパラメータ** を持つことができる。 ライフタイムはその1種 ```rust= // 1つの参照を借用する場合 fn bar<'a>(...) // 2つの場合 fn foo<'a, 'b>(...) // 複数の参照を1つのライフタイムで表すこともできる fn x_or_y<'a>(x: &'a str, y: &'a str) -> &'a str {} // mut参照したいとき fn buzz<'a>(x: &'a mut i32) ``` &mut参照のときは & と mut i32 の間にライフタイムが入る 読み方は、「ライフタイム'aをもつ i32へのmutable な参照&」 ### struct name<>{...} Structもライフタイムを持てる。 構造体がメンバに参照を持つとき、参照元よりも長く生存しないことを 保証する必要があるため。 ```rust= struct Foo<'a> { age: &'a i32 } ``` ### impl<'a> Foo<'a> {...} impl行でもライフタイムを必要とする。 ライフタイム'aをもつimplを、ライフタイム'aをもつFoo構造体が使用するということ ## ライフタイムが有効に働く例 ```rust= struct Foo<'a> { x: &'a i32, } fn main() { let x; // -+ xがスコープに入る // | { // | let y = &5; // ---+ yがスコープに入る let f = Foo { x: y }; // ---+ fがスコープに入る x = &f.x; // | | ここでエラーが起きる } // ---+ fとyがスコープから出る // | println!("{}", x); // | } // -+ xがスコープから出る ``` x: 一番寿命が長い。 & &'a i32 y: ブロック内でスコープが尽きる。 & i32 f: ブロック内でスコープが尽きる。 Foo<'a>{ x: &'a i32 } ライフタイムを定義するということは、 **スコープに名前をつける** ということである。 &'a i32 の参照は、それが借用される中盤の {} の中でのみ使用できるということ ## 'static static ライフタイムは特別なライフタイム。 プログラム全体を通して使われるので、ライフタイムに終わりがなく、常に参照できる。 'staticライフタイムをよく使うのは、文字列リテラルを扱うとき ```rust= let x: &'static str = "Hello, world"; ``` **文字列リテラルの型** : &'static str データセグメント(静的領域)に焼き付けられた値であり、参照は常に有効。 ## static修飾子 変数をstatic領域に定義する。 この場合も、参照は常に有効。 ```rust= static PI: f64 = 3.14; let x: &'static i32 = &PI; ``` ## ライフタイムの省略 人間にとっての使いやすさのため、ライフタイムは省略できる。 入力ライフタイム = 関数の参照引数のライフタイム 出力ライフタイム = 関数の戻り値のライフタイム 以下の3つの明確なルールによってライフタイムを推論する。 - 入力ライフタイムを省略すると、互いに異なるライフタイムが自動割当 - 入力ライフタイムが1つだけなら、関数の戻り値の省略されたライフタイムに割り当てる - 入力ライフタイムが複数のとき、&serf か &mut selfがあれば、省略された出力ライフタイムはselfと同じになる。 ### 省略例 ```rust= // 'static ライフタイムの省略 let s: &str = "hello"; fn print(s: &str); ```