Closed7

Rust for Rustaceansの勉強メモ: 基礎

変数を見るときの2つのメンタルモデル

  1. High-level models
    値に与えれた名前
    ライフタイムや借用を考えるときに便利
  2. Low-level models
    Value Slot
    unsafeなコードや生ポインタを考えるときに便利

変数について、High-levelとLow-levelの2つのメンタルモデルで考えられるようにすると良いらしい。
所有権とライムタイムを考えるときに、そんなに低レベルの表現まで考えても仕方がないので、高レベルのメンタルモデルで考えたほうが見通しが良いということかな

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であることを要求する。なぜなら新しいスレッドは現在のスレッドより長生きするかもしれないから。

型パラメータに'staticって書いてあるのはよく見るから、なぜ付いているのか注意するようにしたい。

Copyになれる条件

Copyになれる条件は単にbitをコピーすることで、その値を複製できること。以下に当てはまる型はCopyになれない。

  • 非Copyな型を持っている
  • 値がドロップされたときに、リソースをデアロケイトしなければいけない

借用

共有参照

コンパイラの最適化は時とともに変化するから、コンパイラが実際にすることではなく、コンパイラがすることを許されていることに対してコーディングしたほうがいいで。というのは意識したい。
共有参照の場合は、共有参照が存在している間はその参照が指している値は変更されないことは想定してよい。したがってRustコンパイラはある関数内で共有参照の背後にある値が複数回読み取られているとき、代わりに、その値を一回だけ読み取ってそれを再利用することは許されているらしい。
コンパイラは何を許されているかという観点も意識したい。

可変参照

コンパイラはその値にアクセスしている他のスレッドはないと仮定する。排他的。
ほとんど所有しているに等しいが、ドロップする責任がない点で所有とは異なる。なので可変参照は勝手に値をmoveしたりできない。二重ドロップになってしまう。

可変参照の背後の値をmoveしたいときは

  • std::mem::takeを使う
  • 上書き
  • std::mem::swapを使う
    などの手段がある。

内部可変性

追加のメカニズム(AtomicCPU命令等)や不変条件が必要

  1. 共有参照から変更可能な参照を取得する
  2. 共有参照のみで値を置き換える

前者 Mutex RefCell UnsafeCell
後者 std::sync::atomic std::cel::Cell

標準ライブラリのCell

不変条件

  1. スレッド間で共有不可能
  2. セルに含まれる値への参照の取得不可能

提供するもの

  • 値のコピーを返す
  • 値を完全に置き換える

不変条件によって

  • 1 -> いつでもmove可能
  • 2 -> 共有参照を通じて、内部の値を並行に変更されることは起こり得ない。

不変条件っていうか契約?Cellについてはもう少し勉強しよう

ライフタイム

Rust初心者が抱きがちなイメージ:ライフタイムはスコープのことである。これは一致する場合が多いが、正確にはライフタイムは参照が有効でなければならない領域の名前のこと。

ライフタイム=スコープだと考えると理解できない事象

下のコードはコンパイルできる。*x=84&mut xを要求している。1でxの共有参照を取っていてrはでスコープ範囲内だからライフタイム=スコープならこれはコンパイルできないはず。

しっかり理解できていないが、フローを考えていて、2でrは使われていないからrは1 -> 2のフローは存在しない。1 -> 3のフローは存在するが、1 -> 2と1 -> 3は競合しない。

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を見よ。

注意点

型がDropを実装している場合、ドロップは使用とみなされる。Dropを実装していない場合、ドロップは使用とみなされない。

型が複数のライフタイムを持つとシグネチャーが複雑になる。必要なときだけそうしましょう。1つのライフタイムだと短い方のライフタイムになる。複数のライフタイムがあって、そのうちのこれが戻り値のライフタイムだよ!と指定したいときに使う。

ライフタイム 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なライフタイムと借用チェッカーの相互作用に変性が関わってくるから。

むずい、、、
https://www.youtube.com/watch?v=iVYWDIW71jk
見よう

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の参照先はドロップされている。。🤪

このスクラップは1ヶ月前にクローズされました
ログインするとコメントできます