Rust の非公式な対話型シェル「rusti」の紹介

  • 33
    いいね
  • 2
    コメント
この記事は最終更新日から1年以上が経過しています。

安全で実行効率のよい(という謳い文句の) Rust にすっかり惚れ込んでしまいましたが、快適に使えるまでの道のりは、まだ長いかもしれません。

Rust を使い始めてすぐに感じたのは、Haskell(GHC)、OCaml、Scala のような REPL が欲しい、ということでした。REPL は Read-Evaluate-Print Loop の頭文字をとったもので、いわゆる、対話型シェルのことです。

例: Haskell(GHC)の場合

% ghci
GHCi, version 7.10.2: http://www.haskell.org/ghc/  :? for help
Prelude> take 5 [1..]
[1,2,3,4,5]

Prelude> :type foldr
foldr :: Foldable t => (a -> b -> b) -> b -> t a -> b

Prelude> :info Foldable
class Foldable (t :: * -> *) where
  Data.Foldable.fold :: Monoid m => t m -> m
  foldMap :: Monoid m => (a -> m) -> t a -> m
  foldr :: (a -> b -> b) -> b -> t a -> b
  Data.Foldable.foldr' :: (a -> b -> b) -> b -> t a -> b
  foldl :: (b -> a -> b) -> b -> t a -> b
  Data.Foldable.foldl' :: (b -> a -> b) -> b -> t a -> b
  foldr1 :: (a -> a -> a) -> t a -> a
  foldl1 :: (a -> a -> a) -> t a -> a
  Data.Foldable.toList :: t a -> [a]
  null :: t a -> Bool
  length :: t a -> Int
  elem :: Eq a => a -> t a -> Bool
  maximum :: Ord a => t a -> a
  minimum :: Ord a => t a -> a
  sum :: Num a => t a -> a
  product :: Num a => t a -> a
    -- Defined in ‘Data.Foldable’
instance Foldable [] -- Defined in ‘Data.Foldable’
instance Foldable Maybe -- Defined in ‘Data.Foldable’
instance Foldable (Either a) -- Defined in ‘Data.Foldable’
instance Foldable ((,) a) -- Defined in ‘Data.Foldable’

Prelude> foldr (+) 0 $ take 5 [1..]
15

このように :type で型を調べながら、ちょっとしたことを試せるので便利です。他にも、ソースファイルを読み込んで、そこで定義されている関数を個別に動かしてみたりもできます。

Rust でもコンパイラーが型推論しますし、型の不一致の時には懇切丁寧なメッセージを出してくれますので、型をきっちり理解できてなくても、気合で乗り切ることが可能です。(気合というのは、コンパイルが通るまで、何度も何度もソースを書き変えてトライするやり方) しかし、どの式がどんな型の値を返すのかをちゃんと理解しておいたほうが、結果的にはずっと時間を節約できるはずです。コンパイル言語であっても、最初は REPL であれこれ試して理解を深めたい(Haskell、OCaml、Scalaのように)。こうして Rust の型についてある程度のセンスがつけば、それ以降は、リファレンスマニュアルを読むだけで、すぐにピンとくるようになるわけです。

そんな時、24 days of Rust というサイトを見つけて、そこで、rusti という非公式の REPL が紹介されていました。試したところ、現在の Rust のコンパイラーの制限により、ごく簡単なことしかできないのですが、無いよりもずっといいものでした。

Docker でサクッとお試しする

rusti は現状はソースコードからビルドしないといけません。コードは GitHub にあります:

ここで問題なのは、rusti はコンパイラー内部の機能を直接使っているので、現状は、Rust の nightly ビルドを使ってしかコンパイルできないことです。そして、あまりにもコアな部分を使っているので、Rust の開発が進むと、すぐに動かなくってしまうのです。rusti のコミットログを見ると、その戦いの歴史がたどれます...(現在も進行中)

Date: Sat Feb 6 13:11:40 2016 -0700
    Fix for nightly (34af2de40 2016-02-05)

Date: Wed Feb 3 18:58:11 2016 -0700
    Fix for nightly (dea183aa8 2016-02-02)

Date: Sat Jan 30 11:55:01 2016 -0700
    Fix for nightly (303892ee1 2016-01-30)

Date: Thu Jan 28 11:41:00 2016 -0700
    Fix for nightly (38e23e8f7 2016-01-27)

数日ごとに直してますね...。ご苦労さまです。

これだと、セットアップの時点で挫折しそうなので、Docker イメージを用意しました。最後のコミットログにある 2016-02-05 版の Rust nightly を使ってます1

Docker がインストールされている環境で、以下のコマンドを実行してください:

$ sudo docker pull quay.io/tatsuya6502/rusti
$ sudo docker run -it quay.io/tatsuya6502/rusti

ちなみに、Dockerfile などは こちら にあります。

これで、rusti を使う準備が整いました。

rusti であれこれやってみる

Docker コンテナーの中で、以下のコマンドを実行します:

# rustc --version --verbose
rustc 1.8.0-nightly (34af2de40 2016-02-05)
binary: rustc
commit-hash: 34af2de4096b3b1c5d3a5b70171c6e27822aaefb
commit-date: 2016-02-05
host: x86_64-unknown-linux-gnu
release: 1.8.0-nightly

# cd ~/rusti
# cargo run
   Compiling rusti v0.0.1 (file:///root/rusti)
     Running `target/debug/rusti`
rusti=>

rusti=> が rusti のプロンプトです。式を入れてみましょう。

rusti=> 3 / 2
1
rusti=> 3.0 / 2
<anon>:18:20: 18:27 error: the trait `core::ops::Div<_>` is not implemented for the type `_` [E0277]
<anon>:18 println!("{:?}", { 3.0 / 2 });
                             ^~~~~~~
<anon>:18:1: 18:31 note: in this expansion of println! (defined in <std macros>)
<anon>:18:20: 18:27 help: run `rustc --explain E0277` to see a detailed explanation
<anon>:18:20: 18:27 help: the following implementations were found:
<anon>:18:20: 18:27 help:   <std::time::duration::Duration as core::ops::Div<u32>>
<anon>:18:20: 18:27 help:   <f64 as core::ops::Div>
<anon>:18:20: 18:27 help:   <&'a f64 as core::ops::Div<f64>>
<anon>:18:20: 18:27 help:   <f64 as core::ops::Div<&'a f64>>
<anon>:18:20: 18:27 help: and 55 others
error: aborting due to previous error
rusti=> 3.0 / 2.0
1.5

f64型をi32型で割ることはできないと。了解です。次はレンジからコレクションを作成:

rusti=> (1..6).collect()
<anon>:18:27: 18:34 error: unable to infer enough type information about `_`; type annotations or generic parameter binding required [E0282]
<anon>:18 println!("{:?}", { (1..6).collect() });
                                    ^~~~~~~
...

collect() の型が推論できないと言ってます。collect() は多相なので、ヒントを与えないと型が確定しません。

rusti=> (1..6).collect::<std::collections::btree_set::BTreeSet<_>>()
{1, 2, 3, 4, 5}
rusti=> (1..6).collect::<Vec<_>>()
[1, 2, 3, 4, 5]
rusti=> (1..6).collect::<Vec<_>>().iter().fold(0, |acc, n| acc + n)
15

上記のように、BTreeSet か Vec かというレベルまで指定すれば、型推論できます。<Vec<i32>> のように、コレクションの中身の型まで指定する必要はありません。

関数も定義できます:

rusti=> fn factorial(n: u32) -> u32 {
rusti.>   match n {
rusti.>     0 => 1,
rusti.>     n => n * factorial(n - 1),
rusti.>   }
rusti.> }
rusti=> factorial(5)
120

.type または .t で式の型を調べられます(.typ.ty も可能)。関数の型も分かります。

rusti=> .type factorial
factorial = fn(u32) -> u32 {factorial}

ベクター型やイテレーター型はどうでしょうか?

rusti=> .type (1..6).collect::<Vec<_>>()
(1..6).collect::<Vec<_>>() = collections::vec::Vec<i32>
rusti=> .type (1..6).collect::<Vec<_>>().iter()
(1..6).collect::<Vec<_>>().iter() = core::slice::Iter<'_, i32>

うーん、イテレーターのところ、惜しいですね。普通の Rust プログラムでは暗黙的に std ライブラリーを use しているので、最後の式は、std::slice::... になるはずです。が、どうやら、rusti は、std だけでなくて core ライブラリーも use しているようで、core::slice::... になってしまいます。まあ、でもこのくらいなら、想像で補えます。

Rust で最初に悩む文字列の2つの型についても確認できます。

rusti=> "Hello" + ", world!"
<anon>:18:20: 18:27 error: binary operation `+` cannot be applied to type `&'static str` [E0369]
<anon>:18 println!("{:?}", { "Hello" + ", world!" });
                             ^~~~~~~
...

rusti=> .t "Hello"
"Hello" = &'static str
rusti=> .t "Hello".to_owned()
"Hello".to_owned() = collections::string::String
rusti=> "Hello".to_owned() + ", world!"
"Hello, world!"
rusti=> .t "Hello".to_owned() + ", world!"
"Hello".to_owned() + ", world!" = collections::string::String

Deref による型強制を使うと、String の参照を str の参照に変換できます。(参考

rusti=> .t &(String::new())
&(String::new()) = &collections::string::String
rusti=> .t &(String::new()) as &str
&(String::new()) as &str = &str

こちらはいわゆる C言語のヌル文字終端文字列。単なるバイト配列です。

rusti=> .t b"Hello, world\0"
b"Hello, world\0" = &'static [u8; 13]
rusti=> .t b"Hello, world\0".to_owned()
b"Hello, world\0".to_owned() = [u8; 13]

まだ制限も多い

まだ発展途上というか、できないことも結構あります。例えば、トレイトで定義されているメソッド(例:std::iter::Iterator::fold())について、引数の型を知る方法がわかりませんでした。

rusti=> .t (vec![0]).iter().fold
<anon>:14:18: 14:22 error: attempted to take value of method `fold` on type `core::slice::Iter<'_, _>`
<anon>:14 (vec![0]).iter().fold ;
                           ^~~~
<anon>:14:18: 14:22 help: maybe a `()` to call it is missing? If not, try an anonymous function

メソッドの値は取れません、と言ってます。でも、もし以下のように書くと、引数の方ではなくて、リターン型(i32)になってしまいます。

rusti=> .t (vec![0]).iter().fold(0, |acc, n| acc + n)
(vec![0]).iter().fold(0, |acc, n| a + n) = i32

結局のところ、コンパイルができない式は、.type で扱えないってことだと思います。

あと、let は使えるのですが、今のところ超局所的で、使いどころがあまりない感じです。なお let は式ではなくて文なので、rusti で入力するときも、末尾に ; が必要です。

rusti=> let a = vec!["a", "b", "c"];
<anon>:18:5: 18:6 warning: unused variable: `a`, #[warn(unused_variables)] on by default
<anon>:18 let a = vec!["a", "b", "c"];
              ^
rusti=> a
<anon>:18:20: 18:21 error: unresolved name `a` [E0425]
<anon>:18 println!("{:?}", { a });
                             ^

上記のように、せっかく定義しても、使う前に忘れてしまいます。生存期間、短かすぎです。

.block ... . を使うと、そのブロック内なら参照できるようになります。

rusti=> .block
rusti+> let a = ["a", "b", "c"];
rusti+> println!("{}", a[2]);
rusti+> .
c

でも、これだと、.type のようなコマンドと同時に使えません。

rusti=> .block
rusti+> let a = ["a", "b", "c"];
rusti+> .type a
rusti+> .
<input>:2:1: 2:2 error: unexpected token: `.`
<input>:2 .type a

この辺の問題は作者の方々も把握しているようですが、コンパイラーの作りにより、実現するのはなかなか難しいようです。今後に期待します。

他にも機能が

まだ試せてないのですが、以下のような機能もあるそうです。

  • .load コマンドで、ファイルから読み込む。
  • extern crate クレート名 で外部クレートを読み込む。ただし、クレートは通常の rlib に加えて、dylib 版も事前に生成しておかないといけない。cargo rustc --lib -- --crate-type=rlib,dylib。 また、rusti の起動時に、-L オプションで、そのパスを指定する必要がある。(-L オプションは何個でも指定できる)
  • コード補完: racer との併用が可能。

詳しくは rusti の README を読んでみてください。

いかがでしょうか? まだ実用的には使えないかな。でも、個人的には、ちょっとした型の学習には使えそうと思いました。



  1. ちなみに、nightly 版をダウンロードしてわかったのですが、ダウンロードディレクトリーの 2016-02-05 には、その前日の、2016-02-04 版 nightly が入ってます。そして、これだと当然のことながら rusti のビルドに失敗しました。つまり、2016-02-06 ディレクトリーからダウンロードしないといけなかったのです