Open10

Rust learning notes

momotaro98momotaro98

Rust のマクロ

#[macro_use] とは?

The #[macro_use] attribute is used to indicate that a crate or module wants to use macros from another crate.

#[macro_use]
extern crate my_macros;

fn main() {
    my_macro!(); // Using a macro from the `my_macros` crate
}
momotaro98momotaro98

Rust の trait について

【確信未だ無し】 struct の trait 実装判断はフィールドに依る(?)

pub struct MyError {
    field1: u32,
    field2: String,
}

上記のような構造体があるとき、field1field2はそれぞれu32, String 型でどちらも Send trait を実装しているので、MyError 構造体もSend trait を実装している、ことになるらしい。(ChatGTPに聞いたらそう答えた)

以下の公式Bookを読んでも記述が見当たらないように見える(2023年6月12日)

https://doc.rust-lang.org/book/ch10-02-traits.html

trait にある type XXX; とは?

例えば Iterator trait には以下のようにtype Item;がある。

pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;
}

これは Associated type と呼ばれるRustの仕様で、traitが持つ型パラメータ とみなしてよい。

そういう意味ではジェネリクスと同じようだが、何が違うかというと、traitを実装する構造体にてジェネリクスでは1つの名前で複数の型で定義できてしまうが、Associated typeならばある構造体にて1つの型で固定し、いちいち型を指定する必要がなくなる。

// ジェネリクスでの定義
pub trait Iterator<T> {
    fn next(&mut self) -> Option<T>;
}

// ジェネリクスでは以下のように複数の型で実装できてしまうが、
impl Iterator<String> for Counter {...
impl Iterator<u32> for Counter {...

// Associate TypeならばCounter構造体のIterator実装は1つの型に絞ることができる。
impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        // --snip--

公式Doc対応ページ

https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#specifying-placeholder-types-in-trait-definitions-with-associated-types

momotaro98momotaro98

エラー non-exhaustive patterns とは?

non-exhaustive patterns というエラーはmatchのパターンマッチ等にて羅列する条件が網羅されておらず、値が返せない(定義されていない)ためにコンパイルエラーになっている。

英単語 exhaustive は "including or considering all elements or aspects; fully comprehensive." という意味。

error[E0004]: non-exhaustive patterns: `XXX` not covered
momotaro98momotaro98

if let Some(x) = XXX { の理解

let は文法仕様として以下である。

let PATTERN = EXPRESSION;

ポイントはPATTERNであり let x = 5; は厳密にはxというパターンに対して束縛(bind)させるのがletの役割である。

let (x, y) = (3, 4, 5); // mismatched types ← (_, _) タプル型に合っていない

let Some(x) = Some(5); //  refutable pattern in local binding ← Noneのパターンがサポートされていない(と論破可能)

refutable pattern を回避するために if let を使う。Someのパターンの場合、直下のスコープに入る。

    // コンパイルは通る
    if let Some(x) = Some(5) {
        println!("{}", x); // 5
    }

https://doc.rust-lang.org/book/ch18-01-all-the-places-for-patterns.html#let-statements

momotaro98momotaro98

Trait object (動的ディスパッチ) vs Generics (静的ディスパッチ)

Effective Rustによると、Genericsの方が望ましいが、Trait Objectを利用しないといけない場面は多そう。

仮にTraitを実装できていても、Genericsの型へプログラムスコープ内で変換することができない説。
自コード具体例

__TODO: __ 詳細を整理したい

trait Shape {
    fn area(&self) -> f64;
}

// trait object (動的ディスパッチ)
fn print_area(shape: &dyn Shape) {
    println!("Area: {}", shape.area());
}

// generics (静的ディスパッチ)
fn print_area<T: Shape>(shape: T) {
    println!("Area: {}", shape.area());
}

fn main() {
    // 動的ディスパッチ呼び出し
    print_area(&rectangle);

    // 静的ディスパッチ呼び出し
    print_area(rectangle);
}
momotaro98momotaro98

Rust の並行処理を理解したい (ずっと)

Arc, Mutex, Condvar を組み合わせた Example でそれぞれを理解

Condvarはこの例のようにMutexと組み合わせて利用されるもの

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

let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair2 = Arc::clone(&pair); // 【重要】Arc::clone によって作られるpair2はpairと同じ実態(同メモリ上)を参照する

// Inside of our lock, spawn a new thread, and then wait for it to start.
thread::spawn(move|| {
    let (lock, cvar) = &*pair2;
    let mut started = lock.lock().unwrap(); // 【重要】Mutex (ここでは`lock`)はある値がそのとき1スレッドのみから変更される制御のためのもの
    *started = true;
    // We notify the condvar that the value has changed.
    cvar.notify_one(); // 【重要】Condvarはスレッド間でno CPU時間で"待ち"→"開始OK"を伝達させるためのもの。
});

// Wait for the thread to start up.
let (lock, cvar) = &*pair; // 上記別スレッドでの&*pair2と同じ実態(同メモリ上)である。
let mut started = lock.lock().unwrap();
while !*started {
    started = cvar.wait(started).unwrap(); // 【重要】ここで一時的にstartedのLockを解除し、cvar.notify_one() を待つ。
    // 【重要】この Condvar.wait(&self, MutexGuard) は引数のMutexのLockを外して待ち通知が来てwaitがreturnを返すときには"再度Lockを取得する"
}

参考スクラップ: https://zenn.dev/senk/scraps/38f841da15d11d

momotaro98momotaro98

Rust のエラーハンドリング

? は即時returnの糖衣構文

?演算子は Result<T, E>またはOption<T, None> を返す関数内で Result<T, E>またはOption<T, None> に対応する関数を呼び出したときに即時returnの記述を簡潔化するための記法である。

以下のコードは

fn read_username_from_file() -> Result<String, io::Error> {
    let mut username_file = File::open("hello.txt")?;
.
.
    Ok(something)
}

以下のコードへコンパイラが変換するとみなして良い。(厳密にはエラー型の扱いが異なる可能性あり ※)

fn read_username_from_file() -> Result<String, io::Error> {
    let mut username_file = match File::open("hello.txt") {
        Ok(file) => file,
        Err(e) => { return Err(e); }
    }
.
.
    Ok(username)
}

Resultを返す関数内でOptionを返す関数を呼び出し?を使えば以下のコンパイルエラーになる。

error[E0277]: the `?` operator can only be used in a function that returns `Result`

https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html

momotaro98momotaro98

Rust のモジュール について

基本の基

The bookの以下がすべてである。

https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html#modules-cheat-sheet

  • ルートが1つだけ存在する
  • mod xxx; がないとxxx.rsはモジュールとして認められない。( ただし mod xxx {} で囲っていればOK )
  • 同じルート内では use はただのショートカット(エイリアス)でありmod参照の上に成り立っている

pub use について

利用側
use moduleA::moduleB::moduleC::MethodD;

のように利用者が知る必要のないmoduleB,Cを知らなくて良いようにする仕組み。

提供側
pub use self::kinds::SecondaryColor;

のようにすることで

利用側
use art::PrimaryColor;

と書けるようになる。

https://doc.rust-lang.org/nightly/book/ch14-02-publishing-to-crates-io.html#exporting-a-convenient-public-api-with-pub-use