Rust for Rustaceansの勉強メモ: 基礎
これを読んでいく。めちゃめちゃいい本っぽい😄
変数を見るときの2つのメンタルモデル
- High-level models
値に与えれた名前
ライフタイムや借用を考えるときに便利 - Low-level models
Value Slot
unsafeなコードや生ポインタを考えるときに便利
Heap
プログラムの現在のコールスタックと紐付けられていないメモリのプール。
RustでHeapを使うための基礎的な方法はBox
型を使うこと。
ヒープメモリをデアロケイトしないと、メモリリークになる。あえてメモリリークさせることもある。(プログラム全体でアクセスできるべきであるRead Onlyの設定情報等)このようなときは'static
な参照を得るためにBox::leak
が使える。
Static Memory
プログラムの実行時に静的にロードされる。プログラムのバイナリコード、static
で宣言された変数等。
'static
ライフタイム -> static memoryが存在する限り有効。'static
ライフタイムは型パラメータのtrait bound
でよく出てくる。
T: 'static
T: 'static
基本的にTが所有されていて、self-sufficient(<-どういう意味や?)という意味になる。Tは非静的な値を借用しないか、静的なもののみ借用する。
例
std::thread::spawn
はそれに渡すclosure
が'static
であることを要求する。なぜなら新しいスレッドは現在のスレッドより長生きするかもしれないから。
Copyになれる条件
Copy
になれる条件は単にbitをコピーすることで、その値を複製できること。以下に当てはまる型はCopy
になれない。
- 非Copyな型を持っている
- 値がドロップされたときに、リソースをデアロケイトしなければいけない
借用
共有参照
可変参照
コンパイラはその値にアクセスしている他のスレッドはないと仮定する。排他的。
ほとんど所有しているに等しいが、ドロップする責任がない点で所有とは異なる。なので可変参照は勝手に値をmoveしたりできない。二重ドロップになってしまう。
可変参照の背後の値をmoveしたいときは
-
std::mem::take
を使う - 上書き
-
std::mem::swap
を使う
などの手段がある。
内部可変性
追加のメカニズム(AtomicCPU命令等)や不変条件が必要
- 共有参照から変更可能な参照を取得する
- 共有参照のみで値を置き換える
前者 Mutex RefCell UnsafeCell
後者 std::sync::atomic
std::cel::Cell
標準ライブラリのCell
不変条件
- スレッド間で共有不可能
- セルに含まれる値への参照の取得不可能
提供するもの
- 値のコピーを返す
- 値を完全に置き換える
不変条件によって
- 1 -> いつでもmove可能
- 2 -> 共有参照を通じて、内部の値を並行に変更されることは起こり得ない。
ライフタイム
Rust初心者が抱きがちなイメージ:ライフタイムはスコープのことである。これは一致する場合が多いが、正確にはライフタイムは参照が有効でなければならない領域の名前のこと。
ライフタイム=スコープだと考えると理解できない事象
下のコードはコンパイルできる。*x=84
で&mut x
を要求している。1でxの共有参照を取っていてrはでスコープ範囲内だからライフタイム=スコープならこれはコンパイルできないはず。
fn main() {
let mut x = Box::new(42);
// 1
let r = &x; // xの共有参照をとる。
if rand::random() {
// 2
*x = 84; // xの可変参照をとる、
} else {
// 3
println!("{}", r);
}
}
これはコンパイルできない。1 -> 2と1 -> 3が衝突する。
fn main() {
let mut x = Box::new(42);
// 1
let r = &x; // xの共有参照をとる。
if rand::random() {
// 2
*x = 84; // xの可変参照をとる、
}
// 3
println!("{}", r);
}
Genericのライフタイム
The Rust Programming Langauage
を見よ。
注意点
ライフタイム Variance(変性 <- 聞き馴染みない🥴)
型Aが型Bのサブタイプである。Aが少なくともBと同じくらい便利である。
記法
正式なものではない
'b : 'a ('bは'aより長生きする) 'bは'aのサブタイプ
変性の種類
- covariant
- invariant
- contravariant
Covariant
その型の場所でその型のサブタイプが使える
例
&a T in T
T in T
Invariant
正確にその型を使わなければ行けない。
&mut T in T
Contravariance
なぜ変性を学ぶ必要があるのか。それはGenericなライフタイムと借用チェッカーの相互作用に変性が関わってくるから。
Crust of Rust: Subtyping and Varicance
strtokをRustで実装している。
コンパイルが通らない。。
ok
fn main() {
let s = String::new();
let x: &'static str = "hello world";
let mut y = &*s;
y = x; // 'static -> a
yのライフタイムは'a xのライフタイムは'static
informalな定義
T: U
Tは少なくともUと同じくらい便利
'static : 'a
covarinace
ほとんど
fn foo(T) {}
let x: T = ;
fn foo(&'a str) {}
contravariance
定義
fn foo(Fn(&'a str) -> ())
使用
foo(fn(&'static str) ->{})
これはOKでしょうか?だめですね。定義がfn foo(Fn(&'a str) -> ())
ということは任意のライムタイムの参照が渡される可能性があります。それなのに'staticなライフタイムを要求する関数を渡してはだめです。
インフォーマルな定義でいうとFn(&'a str) -> ()はFn(&'static str) ->{})より便利です。
つまりFn(&'a str) -> (): Fn(&'static str) ->{})
invaricance
exactory same
fn foo(s: &mut &'a str, x: &'a str) {
*s = x;
}
let mut x: &'static str = "hello world";
let z = String::new();
foo(&mut x, &z);
drop(z);
printtln("{}", x)
もし &mut Tがcovariantならこれはok。ん。。?xのライムタイムは'staticなのにzの参照をいれられて、zがドロップされてしまったぞ。。xがプリントされているが、xの参照先はドロップされている。。🤪