Rustに入門したので、適宜HaskellのこれはRustのこれ、ということをここに蓄積していって、個人的なチートシートにする。
Borrow と Ownership
- 基本的に Region Monad
と線型型(呼ばなくても良いので正確にはアファイン型)が組込みだと思えばよい。
{から}までの間でスコープが区切られ、スコープごとにDrop(=free)がされる。
プロジェクト管理
- Stack に当たるのが cargo
stack.yamlはCargo.toml- コードは
src直下固定。src/main.rsがMain.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{ ... }内部でやった方が適切な場合あり。
- スコープとlifetimeの関係で、条件判定やパタンマッチを
TypeApplications: GHCのTypeApplicationsでのf @T xsに当るのはf::<T>(xs)。ワイルドカードも使える:f::<Vec<_>>(xs)。
データ型
Int16, Int32, Int64, Word16, Word32, Word64に当るのがi16, i32, i64, u16, u32, u64。 ポインタアドレス等、アーキテクチャ依存の整数型(IntやWord)はisize(符号付) やusize(符号無)。structやenumが Haskell のdataやnewtypeに当る。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>。NothingとJust aには{.hs} にはSome(a)とNoneが対応。Optionをpanicに変換するには:fromJust mama.unwrap()fromMaybe (error "unko") mama.expect("unko")
Either a bに対応するのがResult<B, A>Right aOk(a),Left bErr(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は動的にディスパッチするので、ゼロコスト抽象は効かない。