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 (全場合列挙)でないとエラーになる。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
は直積型、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の
?
オペレータと同じようなものがある。とかあったら、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
)- Rust は「ゼロコスト抽象」を謳っており、使用したトレイトの実装は自動的に特殊化されコードが生成される。
- いわば、
SPECIALISE
プラグマが適宜有効化されたような状態になっている。
- いわば、
- デフォルトで
OverlappingInstances
状態。 存在型:トレイト制約付きの存在型(existential type)は
dyn
で作る。に対応するのは、
dyn
は動的にディスパッチするので、ゼロコスト抽象は効かない。