暇人じゃない

Rust 入門日記 (3) ゼロコスト抽象化〜スレッド安全性
Rust

実践 Rust プログラミング入門 を読んで気になったところをメモするシリーズ。

ゼロコスト抽象化

  • 抽象化した処理を実行するために必要な負荷を可能な限り小さくすること

trait & dyn

  • trait

    • トレイトを使ってポリモーフィズムを実現する
    • 様々な型に共通のメソッドを実装するように促すことが出来る仕組み
  • dyn

    • トレイトオブジェクトへの動的ディスパッチ (実行時にインスタンスを決定) ができる
let bird_vec: Vec<Box<dyn Tweet>> = vec![Box::new(dove), Box::new(duck)];
for bird in bird_vec {
    bird.tweet();
}

マーカトレイト

ジェネリクス

fn main() {
    let t1 = make_tuple(1, 2);
    println!("{}", t1.1);
    let t2 = make_tuple("Hello", "World");
    println!("{}", t2.0);
}

fn make_tuple<T, S>(t: T, s: S) -> (T, S) {
    (t, s)
}

所有権と借用

  • 所有権: それぞれの値には所有権があり、所有権を持っているのは必ず 1 つの変数 (所有者) だけ

    • 所有者の変数がスコープから外れたら、その値は廃棄される
    • メモリの二重解放を防ぐことにつながる
  • 借用

    • 関数に値を引数で渡したりすると所有権が移るため、所有権を戻すのに苦労する
    • 値を参照する権利だけを貸し出す
  • ライフタイム

    • 参照は所有者よりも長生きであってはいけない
    • 参照を安全に利用できる期間のこと

RAII (Resource Acquisition Is Initialization)

  • 変数の初期化時にリソース確保を行う
  • 変数を破棄する時にリソースの解放を行う
  • デストラクタとして Drop トレイトが用意されている

スレッド安全性

use std::thread;

fn main() {
    let mut handles = Vec::new();

    for x in 0..10 {
        handles.push(thread::spawn(move || {
            println!("Hello, World!: {}", x);
        }));
    }

    for handle in handles {
        let _ = handle.join();
    }
}
  • move: クロージャが使用している値の所有権を奪う

共有メモリ

  • Rc: 参照カウンタを持つ型。マルチスレッドでは複数のスレッドが同時にアクセスした際に壊れてしまう可能性がある
  • Arc: Rc のマルチスレッド版。Rc より余分なコストがかかるため、シングルスレッドでは Rc を使う
  • Mutex: Arc では書き換えを行うことはできないので、排他制御を行う
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let mut handles = Vec::new();
    let data = Arc::new(Mutex::new(vec![1; 10]));

    for x in 0..10 {
        let data_ref = data.clone();
        handles.push(thread::spawn(move || {
            let mut data = data_ref.lock().unwrap();
            data[x] += 1;
        }));
    }

    for handle in handles {
        let _ = handle.join();
    }

    dbg!(data);
}

メッセージパッシング

  • スレッド間通信用のチャンネルを作成する
use std::sync::mpsc;
use std::thread;

fn main() {
    let mut handles = Vec::new();
    let mut data = vec![1; 10];
    let mut snd_channels = Vec::new();
    let mut rcv_channels = Vec::new();

    for _ in 0..10 {
        let (snd_tx, snd_rx) = mpsc::channel();
        let (rcv_tx, rcv_rx) = mpsc::channel();

        snd_channels.push(snd_tx);
        rcv_channels.push(rcv_rx);

        handles.push(thread::spawn(move || {
            let mut data = snd_rx.recv().unwrap();
            data += 1;
            let _ = rcv_tx.send(data);
        }));
    }

    for x in 0..10 {
        let _ = snd_channels[x].send(data[x]);
    }

    for x in 0..10 {
        data[x] = rcv_channels[x].recv().unwrap();
    }

    for handle in handles {
        let _ = handle.join();
    }

    dbg!(data);
}

Send & Sync マーカトレイト

  • Send トレイト: 型の所有権をスレッドをまたいで転送できる

    • スレッド間を転送してはいけない型を転送しようとするとコンパイルエラーになる
  • Sync トレイト: 複数のスレッドから安全にアクセスできる

About

chocoby (GitHub / Twitter / Email)

個人事業主のソフトウェア開発者です。 Ruby と Rails を使った Web サービスの開発を得意としています。

CurryBu というサービスを作ったり、jp_prefecture という Gem を作っています。