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 { if x == 0 => "zero of A", // ガードみたいなのも出来る A(x) @10 ... 13) => "10...13" A(x// アズパターンみたいのがあるが、所有権の関係で「別名」付けるのは無理そう => { A(x) println!("Non zero A({}) found", x); "boo" }, | C(x) => "Or!" // オアパターンが出来る B(x) | C(x) if x == 0 => "Or!" // if はオアより結合が弱い。 B(x) => "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 { : i64, fld... } enum Enum { i64), A({ val: i32, dull: i16, }, B }
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 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)もある <T>(args: i64, ...) -> result; fn foo} 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
は動的にディスパッチするので、ゼロコスト抽象は効かない。