🦔

Denoで困ったときはZigで何とかなるかもしれない

2023/07/27に公開
2

サードパーティライブラリが無いよ問題

いきなりネガティブな話で申し訳ございません。愚痴みたいなものです。本題ではありませんので読み飛ばしていただいて問題ありません。

Denoユーザーの1人としての個人的な印象として、Denoの勢いは2023年に入った頃から低迷しているように感じます。2022年末からnpm対応によってNode.jsの資産を使えるようになったにも関わらずです。実際のユーザー数はDenoチームではないので分かりませんが、登場から数年経って真新しさも無くなったので、どんなサービスにもありがちなことなのかなと思います。

ありがちとはいえ、ただ低迷しているだけではありません。ここからNode.jsを超えるのはなかなか難しいです。なぜならnpm対応というのは、明示的にNode.jsに対して「生き残っていい」それどころか「あなたが正しい」と宣言してしまったようなものです。Node.jsの正式後継の立場だったはずが、Bunと同じ立場になって後継になれたらいいな争いをしています。

npm対応にはさらに弊害があります。DenoユーザーがDeno用のサードパーティライブラリを開発するモチベーションが下がったことです。Deno用にライブラリを作ってもDenoでしか使えませんが、npmに作ればNode.jsでもDenoでもBunでも使えます。Deno用にまだ誰も作っていないライブラリを先行して作ったとしても、npmに同じことをするライブラリが既にあれば先発優位になりません。

npm対応は悪のように書いてしまいましたが、Bunへ対抗するためにも必要だったでしょうし、企業である以上、収益を上げないといけませんのでDeno Deployユーザーを増やす目的があったのだと思います。こういうのはなかなか難しいですね。あと、書いたことはあくまで私の主観です。

というわけで、前置きが長くなりましたが、Denoのサードパーティライブラリがこれから充実する期待はできません
そして、今はnpm対応も完全ではなく使えないライブラリが多いです。使えたとしても、npmのライブラリは依存関係が多く、Node.jsと同様にライブラリのアップデートに苦労することは間違いありません。

DenoのAPIと標準ライブラリは充実していますが、それでもDenoのAPIと標準ライブラリだけでは足りない機能はどうしたらいいのか ということになります。この問題は、Denoの開発で頻繁に遭遇します。

Wasmという選択肢

この問題の解決法としては、色々とあります。

  • 自分でライブラリを作る
  • 他言語のライブラリをリライトする
  • Deno.Commandでコマンドを呼び出す(実際に使用したときの記事
  • Deno.dlopenでネイティブ言語のライブラリを呼び出す
  • WebAssemblyのモジュールを呼び出す

このうち、今回説明するのはWebAssemblyのモジュールを呼び出すです。

Deno.CommandDeno.dlopenを使った方法は、サーバーを管理していて、そのサーバーのOSに依存する高度なアプリケーションと連携する場合に有効です。そこまでの機能が必要無く、自分で作りたくない場合は WebAssembly(Wasm) はかなり有力な手段になります。

しかもWebAssemblyはDenoでもブラウザでも動きますので、様々な場面で活用できます。

DenoのWasmにおすすめの言語Zig

WasmといえばRustが有名ですが、私はZigを使っています。

理由は4つほどあります。

Zigコンパイラだけでいい

C言語の場合はEmscripten[1]、Rustの場合はwasm-pack[2] が必要になります。そしてそれに合わせたプログラミングが必要になります。
対してZigはコンパイラオプションを付けるだけでWasm向けにコンパイルできます

Zigはそもそもプラットフォーム間の違いを極力吸収するように作られており、その中にWebAssemblyも含まれています。Zigで書かれたプログラムはWasm向けに書かれたものでなくとも、エクスポート用の関数を書いてコンパイラオプションを付けるだけでWasmにできる可能性が高いです。

標準ライブラリが充実している

Zigの標準ライブラリは多いです。CやRustは標準ライブラリが少ないですね。

Denoの標準ライブラリに無くてもZigにあるものは沢山あります。そして、そのままWasmにできるものも多いです。標準を充実させるというのはDenoに通じるところがあります。

Wasmを利用するときというのは、複雑な計算が多いです。よく利用される計算は標準ライブラリにあることが多いです。これがZigを使っている最大の理由です。逆に標準ライブラリに使いたいものが無ければCやRustでコードを探したほうがいいかもしれません。

余計なコードが生成されない

私が使い始めた1年以上前の段階で、RustやC言語はすごく有難いことにwasmファイルだけでなく、それを呼び出すJavaScriptまで生成してくれました。ところがそのJavaScriptはブラウザ専用になっており、Denoで使おうとすると移植が必要でした。このJavaScriptは何をしているのか解析するのが難しいです。
では、wasmファイルだけ使えばいいのかというと、wasmファイルもそのJavaScriptで呼び出されるように設計されているので結局JavaScriptをしっかり理解する必要がありました。

その分Zigはそんな余計なお世話をしてくれません。作ったプログラムがそのままwasmにコンパイルされ、それを呼び出すJavaScriptも自分で0から作ることができます。

RustやCでもJavaScriptを生成せずにやる方法はありそうですが、使い慣れていないためか単純に見つけきれませんでした。

自分で0から細かい制御ができるというのは意外と強みです。
中身の理解が進みますし、何より 無駄なコードが無くなります

私はZigの標準ライブラリのArgon2をWasmにしてみたのですが、RustやCからWasmにしている他の人のファイルサイズが60~70KBなのに対して、半分以下の25KBで済みました。

学習内容が少なく済む

RustやCでWasmをやる場合、その言語を使ったことが無ければ導入障壁が高いです。特にRustは鬼畜だと思います(笑)。
始めるまでに次のようなことを一度に学ぶ必要があります。

  • メモリ・ポインタ
  • 言語仕様
  • パッケージ管理ソフトの使い方
  • Wasm用ライブラリの使い方
  • JavaScriptでWebAssemblyを扱う方法

Zigだと太字のところだけの理解でWasmを始めることができます。

主観ですが、Zig言語自体の難しさはCより難しいと思いますが、Rustよりは易しく感じます。難しさは人によって変わりますので何とも言えません。日本語情報もRustより圧倒的に少ないですし。

実際に使ってみる

今回はZigを勧める理由だけで、次の記事で、実際にDenoからZigで作ったWasmを呼び出して利用してみます。

https://zenn.dev/itte/articles/dc7471ffc2f76f

脚注
  1. clangやzig ccでもwasmにコンパイルできました ↩︎

  2. コメントいただきました。Rustでwasmにコンパイルするときにwasm-packは必ずしも必要ありませんでした ↩︎

Discussion

laysakuralaysakura

Rustの場合はwasm-packが必要になります。(中略)
対してZigはコンパイラオプションを付けるだけでWasm向けにコンパイルできます。

Rustでもコンパイラオプションだけでコンパイルできます。

% cargo new hello
     Created binary (application) `hello` package
% cd hello
% rustc  --target wasm32-unknown-unknown -O src/main.rs
% file main.wasm
main.wasm: WebAssembly (wasm) binary module version 0x1 (MVP)

生成した .wasm をJSから使う場合に wasm-pack を使うべきという点には同意します。

NakamuraNakamura

コメントありがとうございます!

Rustでもコンパイラオプションだけでコンパイルできます。

そうだったのですね。間違った情報でした!訂正いたします。
wasm-packを使った解説が多くて必須と思い込んでいました。知っていたらZigに変更せずにRustを使い続けたかもしれません。