Closed12

bloopのコードリーディングをしつつRustで理解が曖昧な点を調べてみる

moroball14moroball14

tauriを使ったアプリケーション。Rust製のマルチプラットフォームに対応したGUIアプリケーション作成ようのフレームワーク。
https://tauri.app/

moroball14moroball14

バックエンドのエントリーポイントは、 apps/desktop/src-tauri/src/main.rs

moroball14moroball14
#![cfg_attr(
    all(not(debug_assertions), target_os = "windows"),
    windows_subsystem = "windows"
)]

Rustのコンパイラに対して特定のコンパイルオプションを指示するためのもの

cfg_attrは、指定された条件が真の場合に、後者のオプションが適用される

この場合は、debugモードが無効、かつターゲットOSがwindowsの場合、windowsのアプリケーションとしてビルドすることを意味する。

moroball14moroball14

#![cfg_attr(...)] はディレクティブというらしいが、そもそもディレクティブがわからなかった

コンピュータプログラムのソースコードに記述される要素の一つで、そのコードを解釈・変換するソフトウェア(コンパイラやプリプロセッサなど)への指示や指定などを与えるためのもの
ref: https://e-words.jp/w/ディレクティブ.html

なるほど。

ソースコード中にも存在するから、適用範囲は書く場所によって変わるっぽい

macでも開発することがあるから、debugモードが有効とか、ターゲットOSがmacOSの時はwindowsのアプリケーションとしてビルドしないようにしているっぽい

moroball14moroball14
use std::sync::Arc;

このArcってちょっと理解曖昧かも。ChatGPTに聞いてみよう

moroball14moroball14

以下、ChatGPTの解答


use std::sync::Arc;は、Rustの標準ライブラリ(std)のsyncモジュール内のArcという型を現在のスコープに導入するための記述です。

Arcは「Atomic Reference Counting」の略であり、複数の所有者を持つ共有参照カウンタを表します。複数のスレッド間でデータを共有する必要がある場合に有用です。

具体的には、Arcは複数の所有者があり、それぞれがデータへの共有参照を持つことができます。所有者の数は自動的にカウントされ、最後の所有者が参照を解放すると、データが自動的に解放されます。この仕組みにより、スレッド間でデータを共有する際に所有権やライフタイムの問題を解決することができます。

use std::sync::Arc;を使用することで、Arc型を短い形式で利用できるようになります。例えば、Arc<T>という型を宣言する代わりに、単にArcと記述することができます。

以下は、Arcの使用例です:

use std::sync::Arc;

fn main() {
    let data = Arc::new(42); // `Arc`を使用して整数型のデータを共有する

    let cloned1 = Arc::clone(&data); // データの所有権をクローンする
    let cloned2 = Arc::clone(&data); // 別の所有権をクローンする

    // `data`の参照カウントは3になり、3つの所有者が存在する

    // データへのアクセスと操作
    println!("Data: {}", *data);
    println!("Cloned1: {}", *cloned1);
    println!("Cloned2: {}", *cloned2);
}

この例では、Arc::new関数を使用して整数値のデータを共有するArcを作成しています。Arc::cloneメソッドを使用して、所有権をクローンして新しい所有者を作成しています。それぞれの所有者はデータへの共有参照を持ち、データの参照カウントは自動的に管理されます。

Arcは、並行性やスレッド間のデータ共有などのシナリオでよく使用される重要な型です。use std::sync::Arc;を使用することで、短く便利な形でArcを利用することができます。


複数の所有者を持つために、このArcが必要

複数の所有者が必要になるケースは複数のスレッド間でデータを共有する場合か
Rustの日本語ドキュメントでいくと↓あたり
https://doc.rust-jp.rs/book-ja/ch16-03-shared-state.html#arctで原子的な参照カウント

Arc型を使うと内部的に参照の数を数えていて、Rustでは最後の所有者が解放された時にデータを解放することとするらしい

参照カウントって概念が存在していて、それを採用した感じなのか、知らなかった
https://ja.wikipedia.org/wiki/参照カウント

moroball14moroball14

ArcとRcの違い

Rc

  • 単一スレッドの環境で使用
  • ヒープ上のデータへの所有権を共有するためのスマートポインタ

Arc

  • 複数スレッドの環境で使用
  • ヒープ上のデータへの スレッドセーフ な共有所有権を共有するためのスマートポインタ

サンプルコード

use std::rc::Rc;
use std::sync::Arc;
use std::thread;

fn main() {
    // Rcのサンプルコード
    let rc_data = Rc::new(42);
    let rc_clone1 = Rc::clone(&rc_data);
    let rc_clone2 = Rc::clone(&rc_data);

    println!("Rc Data: {}", *rc_data);
    println!("Rc Clone 1: {}", *rc_clone1);
    println!("Rc Clone 2: {}", *rc_clone2);

    // Arcのサンプルコード
    let arc_data = Arc::new(42);
    let arc_clone1 = Arc::clone(&arc_data);
    let arc_clone2 = Arc::clone(&arc_data);

    println!("Arc Data: {}", *arc_data);
    println!("Arc Clone 1: {}", *arc_clone1);
    println!("Arc Clone 2: {}", *arc_clone2);
    
    // スレッド間でArcを共有するサンプルコード
    let arc_data = Arc::new(42);

    let handle1 = {
        let arc_data = Arc::clone(&arc_data);
        thread::spawn(move || {
            println!("Thread 1: {}", *arc_data);
        })
    };

    let handle2 = {
        let arc_data = Arc::clone(&arc_data);
        thread::spawn(move || {
            println!("Thread 2: {}", *arc_data);
        })
    };

    handle1.join().unwrap();
    handle2.join().unwrap();
}
  • Rc::cloneでポインタのクローンを生成する
  • * 演算子でデリファレンスしてアクセスする
    • * 演算子はつけなくてもいいが、ポインタ型のデリファレンスを明示的に示すことができる
      • 値がポインタ型であることが読んで理解できる
    • デリファレンス = 何かを参照しているものの参照先を見ること
  • Arc::cloneでポインタのクローンを生成するが、それらは複数スレッドからの参照が可能
moroball14moroball14

複数スレッド間のデータ共有に関しては注意が必要で、競合が発生する可能性がある。
だから、MutexやRwLockなどを利用して安全なマルチスレッドの処理を実現するのか。

Mutex は同時に一つのスレッドしかデータにアクセスできないようにする
RwLock は複数スレッドから同時に読み込むことはは許可するが、書き込みは許可しない

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    // 共有するデータ
    let shared_data = Arc::new(Mutex::new(0));

    // スレッド1
    let data1 = Arc::clone(&shared_data);
    let thread1 = thread::spawn(move || {
        let mut data = data1.lock().unwrap();
        *data += 1;
    });

    // スレッド2
    let data2 = Arc::clone(&shared_data);
    let thread2 = thread::spawn(move || {
        let mut data = data2.lock().unwrap();
        *data += 2;
    });

    // スレッド1とスレッド2の終了を待つ
    thread1.join().unwrap();
    thread2.join().unwrap();

    // 共有データの値を表示
    let data = shared_data.lock().unwrap();
    println!("共有データの値: {}", *data);
}
  • data1.lockMutex 型のメソッドでその値を可変参照として扱うことができる
  • thread1.join() はスレッドの所有権を奪い、スレッドが終了するまでメインスレッドをブロック
このスクラップは2023/07/09にクローズされました