The Rust Programming Language ノート

Takami Torao Rust 1.25 #Rust
  • このエントリーをはてなブックマークに追加

The Rust Programming Language 2nd ED. からのメモ書き。

Resource Acquisition Is Initialization

RAII は GC に頼らない Rust のメモリ管理の方針。リソース (メモリ領域) の確保と同時に初期化し、スコープから外れると同時に開放するために、リソースを所有している変数を明確に意識しなければならない。

  • Ownership (所有者)

    有効なリソース (値を表しているメモリ領域) はその所有者となる変数が 1 つだけ存在する。参照の代入は所有権のムーブを意味している。

    let s0 = "hello";
    let s1 = s0;
    println!(s0);
    error: expected a literal
      --> src\main.rs:17:12
      |
    3 |   println!(s0);
      |            ^^

    上記で expected a literal は宣言されていない変数を使用したときのコンパイルエラーメッセージ。つまり s1 にムーブした時点で元の s0 のスコープは終了している。

  • スコープ

    リソースはその所有者となっている変数がスコープから外れることで削除される。これはスタックに保存されたリソースでも Box::new() を使ってヒープ上に保存されたリソースでも同じ。デストラクタが定義されている場合はスコープ終了時にデストラクタが呼ばれる。

    {
      let s0 = "hello";    // スタックにリソースを確保
      {
        let s1 = Box::new(Foo());   // ヒープにリソースを確保
        // ヒープ上の s1 はここでデストラクタが呼ばれ開放される
      }
      // スタック上の s0 はここで開放される
    }

    構造体に対するデストラクタ実行後、それぞれのフィールドに対するデストラクタの呼び出しが行われる。

  • コピーとムーブ

    u32 のようなプリミティブ型の代入や Copy トレイトの継承した構造体は単純なバイトレベルでのコピーで行われる。参照は代入は「コピー」と「ムーブ」

    {
      let s = "hello";
    }
  • Borrowing (借用)

    Ownership を変更せず一時的に値を使用する。

    {
      let s = "hello";
    }
  • デストラクタ

    Drop トレイトを継承するとデストラクタを定義できる。リソースは所有者のスコープが終了する時点で (それがスタックに確保されたリソースかヒープに確保されたリソースかにかかわらず) 暗黙的にデストラクタが呼び出される。

    struct Foo {
      bar: u32
    }
    
    impl Drop for Foo {
      fn drop(&mut self) {
        println!("drop()")
      }
    }
    
    let foo = Box::new(Foo { bar: 100 });
    println!("Foo.bar = {}", foo.bar);
    
    Foo.bar = 100
    drop()

変数

  • let (不変変数)

    変数はデフォルトで不変 (再代入不可)。let で宣言すると同時に代入を行わなければならない。

    let x = 0;
    x = 1;
    1 |   let x = 0;
      |       - first assignment to `x`
    2 |   x = 1;
      |   ^^^^^ cannot assign twice to immutable variable
  • let mut (可変変数)

    変数を可変 (mutable; 再代入可能) にするには mut を付加する。

    let mut x = 0;
    println!("x = {}", x);    // => x = 0
    x = 1;
    println!("x = {}", x);    // => x = 1
    
  • const (定数)

    定数には必ず型注釈を記述しなければならない。定数はグローバルを含むどのようなスコープでも使用することができる。定数の右辺値は定数式に限定される (コンパイル時に決定できない値 ─ 任意の計算結果や関数の返値は指定できない)。

    const X:f64 = 1.0;
    const Y:f64 = X * 2.0;
    // const Z:f64 = X.ln();  // => calls in constants are limited to struct and enum constructors
    

    ただし、将来的にはコンパイル時点の関数の呼び出し結果を使用する const fn が準備される (現在は Nightly 版コンパイラのみ)。

  • シャドーイング

    let は同一スコープ内に同じ名前を持つ変数を定義することができる。mut と異なり、割り当てられるのは本質的に別の変数であるため異なる型を使用することができる。

    let x = 1;
    let x = 2 * x;
    let x = x * x;
    println!("x = {}", x);  // => x = 4
    let x = format!("[{}]", x);
    println!("x = {}", x);  // => x = [4]
    

エラー処理

  • ? (question operator)

    Result に対する ? (クエスチョン演算子) は結果が Err であればその場で return する。従って Result を返す関数であれば and_then でネストしなくてもエラーが起きた時点で処理を中断することができる。

    fn f() -> Result<A, B>{ ... }
    let x = f()?;
    // 以下と同じ
    // let x = match f() {
    //   Ok(x) => x,
    //   Err(x) => return Err(x)
    // };
  • Debug

    構造体に Debug 属性を付加することでデフォルトの fmt() を実装することができる。カスタムの文字列化を実装する場合は impl を使用する。

    #[derive(Debug)]
    struct Foo {
      f1: u32,
      f2: f64,
    }
    println!("{:?}", Foo { f1: 1, f2: 0.5 });
    // Foo { f1: 1, f2: 0.5 }
    use std::fmt;
    struct Bar {
      b1: u32,
      b2: f64
    }
    impl fmt::Debug for Bar {
      fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Bar [ x = {}, y = {} ]", self.b1, self.b2)
      }
    }
    println!("{:?}", Bar { b1: 1, b2: 0.5 });
    // Bar [ x = 1, y = 0.5 ]

フレーズ