Rustに入門したので、適宜HaskellのこれはRustのこれ、ということをここに蓄積していって、個人的なチートシートにする。

Borrow と Ownership

  • 基本的に Region Monad と線型型(呼ばなくても良いので正確にはアファイン型)が組込みだと思えばよい。
    • { から } までの間でスコープが区切られ、スコープごとに Drop (=free)がされる。

プロジェクト管理

  • Stack に当たるのが cargo
  • stack.yamlCargo.toml
  • コードは src 直下固定。src/main.rsMain.hs 相当で、src/lib.rs がライブラリのルートに当る。

モジュールシステム

  • 一つの .rs ファイル内で複数のモジュールを、ネストして定義出来る。
  • mod a; のように定義を省略した場合、a.rsの内容がモジュール a の定義として扱われる。
  • foo::bar::buz に当るファイルは src/foo/bar/buz.rs
    • このファイルの存在下では、foo::bar に当るのは src/foo/bar/mod.rs
  • Haskellの import に近いのは use
    • use a::b::c と宣言しておくと、以後モジュール a::b 以下の定義は b::fun などと呼び出せるようになる。

字句構造

  • 基本的に全て式だが、() 返すのが文。
  • 関数の最後で値を返す際には return は不要。
  • ただし式の最後に ; を付けると文になってしまうので、生の式で値を返す場合は ; を付けないように。
  • Haskell と違い、変数はバシバシ shadowing していく文化。
    • 注意:Shadowing された変数にアクセスする方法はないが、shadowing されてもスコープが区切られなければ Drop はされない。
  • コメントは一行コメントのみ:// これはコメントです
  • ここに入れていいのかわからないが、インデントは4スペース。

変数束縛

  • let a: T = b は不変変数宣言。
  • let mut a: T = b は可変変数。
  • static A: T = b は定数宣言。
  • static 以外は型註釈はある程度省略出来る。
  • あとは & とか ref とかの修飾子が適宜つく。

制御構造

  • if に相当するのは if (...) { } else { .. }
    • else 節は省略可。複数の else if を連ねることもできる。
  • case に相当するのは match expr { case => alt, ...} および if let case = expr { ... }
    • match 式は Haskell の case 相当だが、exhaustive (全場合列挙)でないとエラーになる。

      match a {
        A(x) if x == 0  =>  "zero of A",       // ガードみたいなのも出来る
        A(x@10 ... 13)  => "10...13"
        // アズパターンみたいのがあるが、所有権の関係で「別名」付けるのは無理そう
      
        A(x)            => {
          println!("Non zero A({}) found", x);
          "boo"
        },
        B(x) | C(x) => "Or!" // オアパターンが出来る
        B(x) | C(x) if x == 0 => "Or!" // if はオアより結合が弱い。
      
        _ => "baz"
      }
    • if let はHaskellでのpartialなcaseに当る。

      • else if let p = e { ... } else { ... } のように連ねたりfallback で else が使える。
  • ループ:while p { ... } および loop { ... }
    • スコープとlifetimeの関係で、条件判定やパタンマッチを while の条件部ではなく loop{ ... } 内部でやった方が適切な場合あり。
  • TypeApplications: GHCのTypeApplications での f @T xs に当るのは f::<T>(xs)。ワイルドカードも使える:f::<Vec<_>>(xs)

データ型

  • Int16, Int32, Int64, Word16, Word32, Word64 に当るのが i16, i32, i64, u16, u32, u64。 ポインタアドレス等、アーキテクチャ依存の整数型(IntWord)は isize (符号付) や usize (符号無)。

  • structenum が Haskell の datanewtype に当る。

    struct Struct {
      fld: i64,
      ...
    }
    
    enum Enum {
      A(i64),
      B { val: i32, dull: i16, },
    }
  • struct は直積型、enum は直和型。

    • enumの各コンストラクタをvariantと言う。
    • フィールドラベルのついた struct F { a: i64 } と、ついていないタプル構造型 struct F(i64) は全く違うもの。
  • 再帰型は Box<a> などに包んで間接的に参照の形で持つ。

    • フィールドの値や関数の引数は、メモリ上でのサイズが確定していないといけないので。
  • コンストラクタは Enum::A のように呼ぶ。

    • ここでも use が使え、use Struct::*use Enum::* のようにすると、単にA(12)B { val: 12, dull: 54 } のように呼べる。
  • NamedFieldPuns 拡張のようなことが出来、Foo { bar } とやるとスコープにある bar の値が barフィールドbar の値になる。

    • 逆にパターンの来る文脈で let P {x , y} = p とすればフィールド x, y の値が変数 x, y にバインドされる。
  • レコードの更新:d = D { field: a, gield: b } のとき、 Haskellでいう d { field = val } は Rust だと D { field: val, .. d } となる。

  • ベクトル:Vec<T>。リストリテラルのように作るのは vec![1,2,3]

    • 固定長ベクトルは [T; 3] のよう。

モナドと例外・失敗処理

  • モナドはない。
    • でも将来に向けて do は予約語になっているようだ。
  • 例外機構はなく、panicを使って自殺するか、後述の Result を使っていくのがよいとされているようだ。
  • Maybe a に対応するのが Option<T>NothingJust aには{.hs} には Some(a)Noneが対応。
    • Optionpanicに変換するには:
      • fromJust ma == ma.unwrap()
      • fromMaybe (error "unko") ma == ma.expect("unko")
  • Either a b に対応するのが Result<B, A>
    • Right a == Ok(a), Left b == Err(b)
    • ライブラリ毎に type MyResult<T> = Result<T, MyError> のような別名を用意している。
    • 順番に注意!Haskell の流儀では二番目の型引数が「成功」だが、Rustでは最初の型引数が成功値
    • unwrap()expect("foo")は同様に使える。
    • モナドはないが、SwiftやCoffeeScriptの ? オペレータと同じようなものがある。 rs fn proc(opt_arg: Option<i64>) -> Result<i16,SomeError> { let a = opt_arg?.some_method().still_perhaps_failling()?; println!("Hahaha"); { some heavy proc ... } Ok(42) } とかあったら、?が付いてる値が Err(hoge) だった場合、なんかいいかんじの変換が成されて直ちに Err が返るようになる(注:Haskellと違いRustにはearly returnがある)。
      • 但し関数定義(returnが呼べるところ)でしか使えない。

      • qnighy氏の記事によれば、nightly では do catch 構文があるようだが、stableには入ってない模様。

オーバーロード

  • Haskell の型クラスに当るのがトレイト(trait

    trait Trait<'a, T, U>: this::is::SuperTrait {
      type F  // 関連型(associated type)もある
      fn foo<T>(args: i64, ...)  -> result;
    }
    
    impl<'a, T> Trait<'a, T, T> for MyStruct {
      ...
    } 
  • Rust は「ゼロコスト抽象」を謳っており、使用したトレイトの実装は自動的に特殊化されコードが生成される。

    • いわば、SPECIALISE プラグマが適宜有効化されたような状態になっている。
  • デフォルトで OverlappingInstances 状態。

  • 存在型:トレイト制約付きの存在型(existential type)はdynで作る。

    data Showable where
      MkShowable :: Show a => a -> Showable

    に対応するのは、

    struct Showable(dyn Display);
    • dyn は動的にディスパッチするので、ゼロコスト抽象は効かない。

Comments