世界を動かす技術を、日本語で。

Nim 2のレビュー:良い点と悪い点、サンプルコード付き

概要

  • Nim 2 以降、 ORC/ARC がデフォルトのメモリ管理方式
  • C++ライクなRAII やリファレンスカウント、豊富なC++インターフェース
  • 柔軟かつ高性能 なシステム言語で、 Python風の書きやすさ も特徴
  • メタプログラミング多様なバックエンド 対応など、開発の自由度が高い
  • ツールチェーンやLSP など、改善の余地も存在

Nim 2の魅力と現状

  • Nim は1-2年使ってきたが、 過小評価 されがちな言語
  • Nim 2 からは トレースGC が非推奨、 ORC/ARC による C++風RAII が標準
  • ref型 はC++の shared_ptr のようなリファレンスカウント(デフォルトは非アトミック、 --mm:atomicArc でアトミック化可能)
  • Nim 3(Nimony) では atomicArc がデフォルト化予定
  • Carbon language の代替としても実用可能な 生産性実用性
  • C++との高い互換性 (テンプレート、コンストラクタ、デストラクタ、演算子オーバーロードなど)
  • ただし、 C/C++の可読性の高いコード生成 は目指していない点は Carbon と異なる
  • NIF (Lisp風の中間表現)導入で インクリメンタルコンパイルツール改善 を推進中

Nimの特徴と強み

  • 簡潔性Python のような書きやすさ、 最小限のボイラープレート
  • 柔軟性メタプログラミング多様なバックエンド (C/C++/Objective-C/JavaScript)対応
  • 高性能C/C++/Rust などのシステム言語と同等のパフォーマンス
  • RAIIサポートデストラクタ/ムーブ/コピー 対応
  • 手動メモリ管理 も可能( defer--mm:none 利用)
  • クロスコンパイルC/C++/ObjC/JS 向けに出力可能、任意のC/C++コンパイラ利用可
    • importc/importcpp/importjs で既存ライブラリ利用
    • C++テンプレート/メンバ関数/演算子 等もサポート
    • NimScript によるスクリプト的な利用やビルド設定も可能

言語設計と型システム

  • UFCS (Uniform Call Syntax)で プロパティ/メソッド/演算子 を柔軟に記述
  • assert/doAssertアサート指向プログラミング が可能
  • 型システム :バリアント、ディスティンクト型、タプル、enumビットセット
  • ジェネリクス/型クラス/コンセプト による柔軟な型制約
  • イテレータyield による独自データ構造の簡単な反復
  • result変数 の自動用意で QoL向上
  • asyncマクロ によるAST変換で実装( asyncdispatch/chronos エンジンあり)

メタプログラミング

  • 型関係式when文 によるコンパイル時分岐
  • fieldPairsプロシージャオーバーロードシリアライズ/デシリアライズ が容易
  • カスタムプラグマ でフィールドへのメタデータ付与
  • 任意のコンパイル時コード実行 (例: const myFileContents = readFile("file.txt")
  • マクロ でAST変換、 テンプレート で安全なコード展開
    • テンプレートはハイジーン的 (スコープ汚染防止)

シンプルなKey/Valueファイルフォーマットの実装例

  • fieldPairsプロシージャオーバーロード の組み合わせで、 任意型のロード/パース を実現

  • 例:Config型の定義と、対応するテキストファイル

    type Config = object
      name: string
      lr: float
      betas: seq[float]
    
    name=my experiment
    lr=0.001
    betas=[0.99, 0.999]
    
  • parseValue のオーバーロードで型ごとにパース処理を記述

  • load[T] 関数でファイルを読み込み、 型T のインスタンスを生成

  • seq[T] 型への対応も parseValue の追加オーバーロードで実装

  • 実行時 にも コンパイル時 にもロード可能(let/constの使い分け)

Nimの課題・改善点

  • LSP(Language Server Protocol) の速度や安定性に課題
  • ツールチェーンコンパイラオプション の理解が必要
  • 細かい不満点 はあるものの、 生産性を大きく阻害しない

まとめ

  • Nim 2現代的なメモリ管理高い柔軟性 を持つシステムプログラミング言語
  • C++やRust、Carbon の代替としても検討可能
  • メタプログラミング多様なバックエンド簡潔な記法 で開発効率を大幅に向上
  • ツール面 にはまだ課題が残るが、 言語本体の完成度は高い
  • 実用例 を通して、 Nimの特徴的な設計思想 を体感可能

Hackerたちの意見

これはまあまあの概要だけど、いくつかの良い点が抜けてるね。興味がある人は、これが全てだと思わない方がいいよ(一般的に、そう思わない方がいいけど)。例えば、その機能がすごく珍しいから(物議を醸す?)あまり言及されないけど、Nimでは自分で演算子を定義することもできるんだ。だから、C系のプログラミング言語からビット演算の |= が missing だと思ったら、こう書けばいいよ:proc |=*[T,U](a: var T, b: U) = a = a or b。もちろん、Nimにはビットセットを管理するための set[T] があって、交差や和などの伝統的な集合論的演算子を使うと、もっと便利に管理できるよ。例えば、https://github.com/c-blake/procs は set[enum] を使って、どの Linux /proc ファイルを読み込むか、どのフィールドを解析するかの依存関係分析をしてるんだ(Cの procps の代替よりもずっと速いしね)。このユーザー定義の演算子の記法は、テンプレートやマクロにも使えるから、カスタマイズされた記法やドメイン特化型言語(DSL)を作るのがすごく簡単になるよ。それに、プラグママクロを使えば、特別なコンパイル時の動作を定義にタグ付けするのも簡単だしね。まあ、そんな感じ。

興味がある人のために、優先順位は演算子の名前に基づいているよ。https://nim-lang.org/docs/manual.html#syntax-precedence

それいいね!たまにCで恋しくなるのが||=なんだよね。ビット演算子には|=があるのに、ショートサーキットがないから全然違うのに混同しちゃいけないんだよね。

そうだね、もうちょっとわかりやすく書けばよかったかな。でも、「これはNim 2の機能の一部」って言ったし、Nimのマニュアルのコピペになりたくなかったんだ。投稿はすでに結構長いから、自分が偏っている機能を優先することにしたんだよね。それに、言語設計の最初のポイントでオペレーターのオーバーロードについても触れたけど、もっと強調すればよかったかも。

Nimの一番クールなところは、マクロが型システムやオーバーロード解決に参加して、型チェックされたコードとされていないコードの両方で動くところだね。

このテンプレートやマクロの型ディスパッチの「二重性」は、スコープルールがテンプレートやマクロを使って、サブスコープ内だけで「有効」なものを定義できることも意味してるんだ。例えば、古いCのポインタ演算の危険性みたいにね - 「定義されてるけど含まれてる」ってやつ。https://forum.nim-lang.com/t/1188#7366 ちょっとしたところで、Nimは静的型付けのLispに、ややPythonっぽい表面構文を持った感じだね。ただ、これだけじゃNimコードを書くときの選択肢の多さを十分に評価できてないけど。

これが俺を実際にNimを試してみようと思わせるコメントだわ。

WASMは標準ライブラリではサポートされていません。Cの出力を使ってemccを使えば、この問題は解決できる?

そうそう - https://github.com/treeform/nim_emscripten_tutorial ちなみに、emscriptenが出てからずっと人々はそうしてきたけど、この記事は標準ライブラリや標準コンパイルツールチェーンとのより密な統合が欠けてることを指摘してるね。一般的に、標準ライブラリを進化させるのは大変だと思う。でも、言語とコンパイラはほとんどのものよりも柔軟だから、Nimではそれほど重要じゃないかもね。

はっきり言うと、emscriptenを使ってWASMにコンパイルできるよ。ただし、ブラウザのWASMからDOMとやり取りするためにJavaScriptコードを呼び出したい場合、標準ライブラリで宣言されたバインディングは使えないんだ。自分でバインディングを書く必要があるよ。ここに、emscriptenを使ったNimの例のリポジトリがあるよ: https://github.com/miguelmartin75/nim-wasm-experiments。あと、websockets用のemscriptenのC APIへのバインディングもここにあるよ: https://github.com/miguelmartin75/nim-wasm-experiments/blob/...。emscriptenを使えば、NimでEM_JSマクロを使ってJavaScriptへのバインディングを作成できるよ。やり方の例はここにある: https://github.com/treeform/windy/blob/bc98d4642c700f0277551...

実際、Nimは今後のCarbon言語の生産準備が整った代替として使えるかもしれません。NimはC++との素晴らしい相互運用性を持っていて、テンプレートやコンストラクタ、デストラクタ、オーバーロードされた演算子などをサポートしています。ただし、Carbonの目標とは異なり、読みやすいCやC++コードにはコンパイルしません。まあ、Carbonに興味を持つ理由によるけど、最後の文で少しほのめかされてるね。私の理解では、大きな目標はGoogleの大規模なC++コードベースをより健全な言語に自動移行できるようにすることだと思う。Mondがそれについていいブログ記事を書いてたよ[0]。Nimはそれじゃないけど、もちろんCarbonもまだそうじゃないし、どこまで行くかは見てみないとわからないね。Carbonを応援してるけど、いいアイデアだと思う。とにかく、既存のC++コードを変更せずに使える後継言語を探すのとは違う野望だね。ここでNimが得意とされていることだよ。 [0] https://herecomesthemoon.net/2025/02/carbon-is-not-a-languag...

C++との「素晴らしい相互運用性」についてはちょっと懐疑的だな。もしそんなに簡単なら、Rustの人たちがもうやってるはずだし、まだ模索してるみたいだしね。RustはC++と共有されているLLVM用に開発されてるし。

NimはC++との素晴らしい相互運用性を持っている 昨年、Nimコミュニティで「C++の相互運用性」があれば、NimでC++ライブラリ(この場合はWickedEngineという3Dエンジン)を簡単にリンクしてインポートできるか聞いたことがあるんだけど、全く簡単な方法はなさそうだった。古いC APIをインポートすることはできるけど、NimをC++コードにトランスパイルすることもできるかもしれない。でも「素晴らしい相互運用性」ってのは、俺の想像とは違ってて、@importcpp "../libwickedengine/compilecommands.json"みたいにして、あっという間に完了、LSPのオートコンプリートも含めて、って感じだったら良かったのに。他の主要なC++ライブラリでも同じことが言えるよね。LLVM、Dear Imgui、Qt、OpenCV、libtorrent、FLTK、wxWidgets、bgfx、assimp、SFML…… 確かに、「Cとは違って、C++にはABIがない。これらのC++ライブラリは基本的なC APIを維持して公開すべきだ」ってのは分かる!でも、やっぱり…

Nimでの作業が大好きなんだ。今のところおもちゃみたいなプロジェクトしか書いてないけど、めっちゃ速いよ。いいIDEや言語プラグイン見つけた人いる?

公式の言語サーバーはあるの?

https://nim-lang.org/docs/nimsuggest.html これをneovimにMasonでセットアップしたんだけど、結構早くて簡単だったよ。ただ、俺の好みの環境はJetBrains系だから、そっちで最新のプラグインがあったら嬉しいな。

私はNimで書かれた実運用中のセンサー分析バックエンドプログラムの開発者だよ。サーバースクリプトが個別またはバッチのレコードを呼び出すから、常に動いてるわけじゃないんだ。シェルを通じて無料で並列処理ができるし、Datamancerのデータフレームをたくさん使ってる。プログラムは完全に処理ロジックだけで、40,000行中にメモリセマンティクスのコードは3行くらいしかないよ。コレクションみたいな動的型はスタック管理された隠れたユニークポインタとして扱われるNimのデフォルトの動作に頼ってる。パフォーマンスは素晴らしいよ。NimのパフォーマンスをC++と比較するためにサイドでいくつかの演習をやったけど、-d:releaseのNimはC++の-O3と同じくらいの結果を出すんだ。特別なメモリトリックとかは使ってないし、ただ非常にPythonicでクリアなコードを書いてるだけ。何でも聞いてね。

環境管理の話はどうなってるの?

最適なパフォーマンスを得るためにどのコンパイルオプションをおすすめする?でも、メモリセーブも考慮してね。(メモリセーブ使ってるよね?)今は「nim c --opt:speed」を使ってる。ほかの言語(主にGoやRust)と比べると、私のユースケースではランタイムパフォーマンスが少し遅い場合もあるんだ。うーん、C++と比較するとメモリセーフを無効にしてるかもしれないね...

Nimの独特なケースセンシティビティのルールは、HNでよく熱い議論のポイントになるから、Nimonyのドキュメントからのこの引用が興味深いかもと思ったよ。 > Nimonyはほとんどの他の現代プログラミング言語と同様にケースセンシティブです。これは実装のシンプルさのためです。将来的には変更される可能性もあります。

これは実装のシンプルさのためです。 本当の理由はgrepやグローバル検索・置換・LLMを容易にするためだよ。

jsバックエンドについてだけど、生成されるアーティファクトのサイズはどうなの?「JSにトランスパイルする」言語の比較を見たことがあって、KotlinとNimが他の言語が出してる数十KBや低い100KB台に対して、MB単位のJSを出してるってのが印象に残ってるんだよね。

ここにそのことについて書いた記事があるよ: https://summarity.com/nim-alpine

何でもそうだけど、コードの内容や使うライブラリによるけど、ちょっとした例を挙げると、無駄にメガバイト出力の心配を払拭できるかもね。echo echo 1 > j.nim nim js j.nim node j.js >>> see 1\n >> 36636 Sep 1 12:54 j.js >> 11369 Sep 1 12:56 j.js だから、-d:releaseでデバッグロジックをかなり削除すれば、そんなに悪くないよ。d:releaseでも、j.jsのテキストの約50%はCのコメントで、簡単に削除できるものだからね。例えば、cpp<j.js|wc -cでその11369ファイルは6350になる。出力に対してJSのミニファイをかけることもできるし。人々はこれについて文句を言うけど、文句を言う人は多いからね。あまりにもトリビアルなプログラムじゃなければ、そこまで競争力がないわけでもないと思うよ。

Nimに対する俺の(個人的な)問題は、すべてがUnixの宇宙を前提にしてることなんだよね。これは(1)Windowsで何かをやりたいときと(2)Nimbleから他のNimライブラリを使いたいときには良くない。NimはMSVCコンパイラを使うことを許可してくれるけど、良いライブラリの多くはそうじゃなくて、コンパイラオプションを直接渡して「とにかく動かせ」みたいな感じでGCCを強制するんだ。前にNimのMatrixチャットでこの話をしたとき、ちょっと... hostileな反応が返ってきたんだよね。侮辱される感じではなくて、「兄弟、Linux使えばいいじゃん」って感じで。もしかしたら状況は変わったかもしれないけど、Nimを使ってたときは、Nimbleライブラリの問題を除けば、結構快適な言語だと思ったよ。

もう一つの問題は、Windows DefenderがNimで生成された実行ファイルを見ると、理由もなくマルウェアだと判断することなんだよね。これがあると、Windows向けの開発をしたいときに最初から厳しい状況に置かれることになる。これが「兄弟、Linux使えばいいじゃん」っていう議論につながる。Windowsは面白いかもしれないけど、商業ソフトウェアを作って、それを見たすべてのアンチウイルス作者に「お願いだから.exeを隔離しないで」って頼むのを想像してみて。これは、数年前に誰かがLinuxでハードウェアを動かす方法を聞いたときによくあった「兄弟、Windows使えばいいじゃん」っていう議論に似てるよね(今でもそうだけど、Thinkpadのバッテリー寿命を延ばそうとするたびに)。

実は、今はnimbleよりatlasの方が好きなんだ(詳しくは: https://github.com/nim-lang/atlas)。依存関係をプロジェクトに対してローカルなdeps/フォルダにクローンしてくれるんだ。あと、ローカルフォルダをnimbleパスに追加するのもサポートしてるよ(atlas linkで - 詳細はここ: https://github.com/nim-lang/atlas/blob/master/doc/atlas.md#l...)。つまり、依存関係を自分でgitサブモジュールや手動のgitクローンでクローンできるってこと。MSVCのためにプラグマをサポートするコードをフォークしてパッチを当てれば、問題が解決するよ。それから、atlasでは自分のフォークのgitパスを使って、必要ならプロジェクトにPRを出すこともできる。何かが動くまで、ローカルでプロジェクトを編集できるからね。

UFCSと暗黙のresult変数は、Eiffelプログラミング言語を思い出させるね。