強まっていこう

あっちゃこっちゃへ強まっていくためのブログです。

第二プログラミング言語として Rust はオススメしません Nim をやるのです

これから Rust 触ってみようかなぁと思っている方。やめておきましょう。プログラミングが嫌いになりますよ。

Nim をやりましょう。

すでに Rust に挑んで心ぶち折れた方。

Nim をやって心の傷を癒やしましょう。

Rust がディスられる!小癪な!と思っている方。

Nim をやるのです(天に轟く反響音)。

Nim の魅力を Rust と比較しつつ皆様へお伝えしましょう(両手を広げる)。

さぁ、みんな、Rust なんて捨ててさっぱり人気が無い Nim をやるのです(ドドメ色に輝く)。

行末のセミコロンが必要ない

タイプ数がもりもり減ります。

Rust にはもちろん必要です。

main が要らない

スクリプト言語感覚でいきなりコードを書けます。

Rust は main が必要です。

標準出力への文字列出力が楽

Nim では echo で改行付きの出力ができます。shell と同じですね。通常は改行付きで出力することの方が多いでしょ。
Nim はしょっちゅうやることは簡単にできるようになっています。
そんな Nim の echo は可変引数で値を受け取り型が何なんだろうとお構いなしに出力できます。

let n = 10
let str = "HOGE"
echo "Number: ", n, " String: ", str

一方 Rust は

let n = 10;
let str = "HOGE";
println!("Number: {} String: {}", n, str);

なんかよく判らんマクロでいちいちびっくりさせなきゃいけないです。よく使うものが冗長だとゲンナリします。
変数を直接ぶち込むことも出来ませんしね。

let n = 10
echo n

普通出来るでしょこんなもん・・・。ところが Rust は出来ない。

let n = 10;
println!(n);         <- エラー
println!("{}", n);   <- 毎度これを書かされる

うざいっす。

C の関数を気軽に持ってくる

たった一行足すだけで C の関数を使うことが出来るようになります。

proc printf*(format: cstring) {.header: "<stdio.h>", importc: "printf", varargs.}
let n = 10
let str = "HOGE"
printf "Number: %d String: %s\n", n, str

どうですこれ?C の資産を気軽に使うことができるんです。SWIG 等の鬱陶しいラッパーを使うこと無くです。
Rust の場合はご多分にもれずラッパー行きで超絶面倒くさいです。比較用に書きたいんですが結構な文章量になるのでやめます。

mut mut しなくて良い

Rust はまともな変数を使おうとすると mut mut しないといけません。デフォルトだと再代入できませんから。
普通再代入しまくりますよね?定数ライクに使いたい機会なんて殆どないですよね?なのに mut を毎度書かされます。

let n:int = 10
let mut m: int = 10

Nim ならこうですよ。

let n = 10 # immutable
var m = 10 # mutable

素敵。

所有者・借用なんてもんでイライラしない

 Rust には C のポインタが可愛く見えるレベルで高くそびえ立つ鉄壁の初心者ガード、悪夢の"所有者・借用"の概念が存在します。
プログラムに慣れた人間ですら混乱に陥れ、書いている最中に精神力と人生の貴重な時間をガンガン削ってくれる究極の嫌がらせです。

Rust は変数のコピーしちゃうと元のやつが使えなくなるクソ仕様なのです。書き手にメリットなんて一切無い。C++の悪しきメモリ管理の呪いを持ち込んで来てその中でもさらに悪い部分をデフォルトにした感じです。

struct Point {
  x: i32,
  y: i32,
}

fn Print(p: Point) {
  println!("x = {}, y = {}", p.x, p.y);
}

fn main() {
  let mut a: Point = Point{ x: 10, y: 15 };
  Print(a);
  // エラー!
  println!("x = {}, y = {}", a.x, a.y);
}

Print(a) で1回コピーされているのでその後使うと死にます。ウソでしょ?と思うでしょ?ホントです。
そしてプリミティブ型ならOKと言う Java に似たダブスタの呪いもオマケで付いてます。

おかげさまで関数はほぼ全て明示的に参照渡しをするハメになります。
「だったらデフォルトそうしとけよ! & をイチイチ書かせんなやワレ!」と思わないのってある種の才能だと思います。

struct Point {
  x: i32,
  y: i32,
}

fn Print(p: &Point) {
  println!("x = {}, y = {}", p.x, p.y);
}

fn main() {
  let mut a: Point = Point{ x: 10, y: 15 };
  Print(&a);
  println!("x = {}, y = {}", a.x, a.y);
}

これだとまぁエラーにはなりません。が、参照だからといってこんなことやったら死にます。

fn Print(p: &Point) {
  println!("x = {}, y = {}", p.x, p.y);
  p.x = 10; <- die
}

イミュータブルだからですって。はぁ・・・。

だからこう書けですって。

fn Print(p: &mut Point) {
  println!("x = {}, y = {}", p.x, p.y);
  p.x = 100;
}

fn main() {
  let mut a: Point = Point{ x: 10, y: 15 };
  Print(&mut a);
  println!("x = {}, y = {}", a.x, a.y);
}

はい来た。mut mut mut mut mut mut mut mut mut ああぁぁああぁ~~~!!!

なんでよく使う方を面倒臭くしたがるんですか、この言語を作っている方々は。

その他又貸しの呪いやらなにやら超盛り沢山ですし。もうね私とはセンスが全く合わないです。

ぬぅぅうぅうぉぉおぉおぁぁあぁあああ!!!!!Rustよ!もうお前には頼まん!malloc と free を俺によこせうぉるぅぁあ!!こんな訳のわからんものに付き合わされるんだったら自分でメモリ管理した方がマシだわ!!!

とよくみんな発狂しませんよね。我慢強いですね。馬鹿じゃないの。

とっても良い子である Nim にはこんな呪いある"ワケ"がないです。

type Point = object
  x: int
  y: int

proc print(this: Point) =
  echo "x = ", this.x, ", y = ", this.y

var p = Point(x: 10, y: 15)
p.print()
echo "x = ", p.x, ", y = ", p.y

まぁ普通はこうですよね・・・。Rust がぶっ飛んで異常なだけです。ありえないです。

ちなみに Nim の場合 print(p) と p.print() は書き方が違うだけで意味は同じです。

参照で渡す場合はこうなります。

type Point = object
  x: int
  y: int

proc print(this: ref Point) =
  echo "x = ", this.x, ", y = ", this.y
  this.x = 100

var p = Point.new
p.x = 10
p.y = 15
p.print()
echo "x = ", p.x, ", y = ", p.y

new で Point object を作成すると参照のオブジェクトが出来ます。これを渡すために print 側の引数には ref をつけてあげます。new 関数でメンバに値を割り当てることは出来ないので後から渡してやります。

つっても上のやつはあくまで Rust と似せて書いたらこうなるよって話でこんな書き方しません。
普通オブジェクトなんて参照だろ、って事で Nim では以下のように書くのが慣例化しています。

type
  Point = ref PointObj
  PointObj = object
    x: int
    y: int

proc print(this: Point) =
  echo "x = ", this.x, ", y = ", this.y
  this.x = 100

var p = Point(x: 10, y: 15)
p.print()
echo "x = ", p.x, ", y = ", p.y

オブジェクトとそのリファレンスを同時に定義して、通常使わない方のオブジェクト側にサフィックスをつけておくと、まぁ素のオブジェクトも作りたきゃ作れるし、って話です。

自分は正直リファレンスだけで良いので更に手を抜いてこう書きますけどね。

type
  Point = ref object
    x: int
    y: int

パターンマッチ?case でしょ?

Nim も case でそれっぽく書けます。

複式パターン
fn main() {
  let x = 1;
  match x {
    1 | 2  => println!("1 | 2"),
    3      => println!("3"),
    _      => println!("other"),
  }
}
let x = 1
case x
  of 1, 2: echo "1 | 2"
  of 3: echo "3"
  else: echo "other"
範囲
fn main() {
  let x = 1;
  match x {
    1...5   => println!("1...5"),
    _       => println!("other"),
  };
}
let x = 1
case x
  of 1..5: echo "1..5"
  else: echo "other"
case の返りを受け取る
fn main() {
  let x = 1;
  let s = match x {
    1     => "one",
    2     => "two",
    _     => "other",
  };
  println!("{}", s)
}
let x = 1
let s = case x
  of 1: "one"
  of 2: "two"
  else: "other"
echo s
分配束

Nim は標準ではできませんが

https://github.com/andreaferretti/patty

を突っ込むことで可能です。

仕様バグがない

Rust の以下の挙動は全く理解ができません。

fn main() {
  let x = 'x';
  let c = 'c';
  match c {
    // x: c c: c
    x => println!("x: {} c: {}", x, c),
  }
  // x: x
  println!("x: {}", x)
}

普通 x にマッチすると思わないでしょこれ。
さらにその直後 x が 'c' に変わってるとか予想だにしませんよ。
まぁ普通はこんな書き方しないと思いますがこんな調子ではどこでどうハマるか予測不可能です恐ろしすぎます。

Nim はこんな書き方そもそも出来ません。

コンパイラがケチくさくない

nim c -r hoge

これで hoge.nim をコンパイルします。
拡張子なんて指定する必要ありません。
-r で実行もします。

Rust の場合

rustc hoge <- ダメ

コンパイルと同時に実行しようと思ったら

rustc hoge.rs && ./hoge

うーん・・・

実行速度・メモリ使用量・ファイルサイズが小さい

Rust と比べて Nim の実効速度はどっこいかむしろ速いです。
Rust はこんだけイライラする書き方を強制されるにも関わらずたいして速くないとかもう哀れすぎます。

コンパイル後のファイルサイズは話にならないレベルで比べ物になりません。

fizzbuzz の例(FizzBuzz を無駄にベンチマークしてみた By Nim、golang、Rust、Crystal、その他 - 強まっていこう)で言うと

項目 Nim Rust
実行速度 0.37s 0.44s
ファイルサイズ 82K 3.4M
メモリ 356K 900K

こんな感じです。

マクロのシンタックスを別で覚える必要がない

Rust のマクロは構文が全く変わってしまいます。そしてそれは脳が全力で受付を拒否する素敵な仕上がりとなっております。
公式で自ら「マクロベースのコードの欠点は、組み込みルールの少なさに由来するそのコードの理解のしづらさです。」と言いのけちゃう代物で「なんじゃそりゃ」と言う言葉しか出ません。

Nim は構文がそのまま使えます。なので強力なマクロを使いこなすための障壁の低さは比べ物になりません。

Rust の良いところ

さすがに Rust を批判ばかりしていては公平性に欠ける報道となり官邸から怒られます。Rust にも良いところはあります。

fn <- 短い!

proc <- 長い!

これはメリットですよ。タイプが2回ずつ減るのは素敵なことです。しっかしこれだけ馬鹿げた冗長さを押し付けてくる言語のくせして、何故ここだけすっきりしているのやらさっぱり意味がわからないです。あ、結局ディスってもうた・・・。