Closed7

Rust for Rustaceansの勉強メモ: 基礎

hayaohayao

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

  1. High-level models
    値に与えれた名前
    ライフタイムや借用を考えるときに便利
  2. 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な型を持っている
  • 値がドロップされたときに、リソースをデアロケイトしなければいけない
hayaohayao

借用

共有参照

可変参照

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

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

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

内部可変性

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

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

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

標準ライブラリのCell

不変条件

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

提供するもの

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

不変条件によって

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

ライフタイム

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

注意点

hayaohayao

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

hayaohayao

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

このスクラップは2021/10/24にクローズされました