17
16

開発ツールに頼らず 様々な言語から WebAssembly(第1回)

Last updated at Posted at 2023-07-03

WebAssembly は多くのプログラム言語からライブラリの様に呼び出す事が出来る。
WebAssembly のコードを書く方法は多くの記事で書かれているが、その使い方はフレームワーク等のツールに頼っている事が多い様だ。
本記事では、そのような便利ツールに出来るだけ頼らず JavaScript, Rust, Python, Ruby から WebAssembly を実行する方法を記載する。

WebAssembly は新しい技術である。
目先の最先端ツールに飛びつくのもよいが、その基礎を学んで長く使える知識を身に着けないか?

シリーズ記事一覧

シリーズ記事が追加された時はこの記事一覧を編集して通知を行うので、ご興味を持っていただけましたら本記事をストックしてください。

WebAssembly を動かす為に必要な物

JavaScript には WebAssembly を動かすためのグローバルオブジェクト WebAssembly が既に言語仕様に組み込まれている。
そのため、フレームワークもライブラリも無しに WebAssembly のコードを実行する事が可能である。

WebAssembly 最初に「JavaScript で動かす」事から始まった。「WebAssembly の出来る事」とは本原稿執筆時(2023 年 6 月)では「JavaScript のグローバルオブジェクト WebAssembly で出来る事」とほぼ同義であると筆者は認識している。

残念な事に他の言語では JavaScript の様な例は少ない様だ。
例えば Rust では(コンパイラーが有れば)特別なツール無しに WebAssembly のコードを書く事は出来るのだが、書かれた WebAssembly のコードを動かすには別途ライブラリが必要になってしまう。

wasmtime は多くのプログラミング言語で使用可能な WebAssembly を動かす低レイヤーのライブラリだ。
多くの有名なフレームワークや開発ツールも、内部的に wasmtime を使用している事が多い。
使ってみると、JavaScript のグローバルオブジェクト WebAssembly を移植したような印象を受ける。

wasmtime の開発には WebAssembly や WebAssembly System Interface の仕様策定のコアなメンバーが関わっている。
近い将来「WebAssembly の出来る事」が「wasmtime で出来る事」と同義になるかもしれないと筆者は予想している。

蛇足だが「wasmtime は WebAssembly System Interface のランタイムではないか?」と疑問に思う人も居るかもしれない。
筆者も以前 別の記事 でそのように書いた。
上記はライブラリの wasmtime を用いて実装した、WebAssembly System Interface のランタイムの事だ。
混乱を避けるため、ランタイムの事を wasmtime-cli と呼ぶ事も有る。
(本記事には wasmtime-cli は登場しない。)

本記事では、下記の方法を紹介する。

  • Vanilla JS (フレームワークやライブラリを使わない、そのままの JavaScript)から WebAssembly を実行する
  • Rust, Python, Ruby からライブラリの wasmtime を使って WebAssembly を実行する

なお、説明の都合上 Vanilla JS から実行 の章は JavaScript に興味が無くとも読んでほしい。
(そんなに難しい事はやっていないので JavaScript を知らなくとも本質的な部分は理解できるはずだ。)
それ以外の興味の無い言語の章については、読み飛ばして問題無い。

復習

本章は下記の記事の簡単なまとめです。
詳細は記事をご覧ください。

System Call と User Land

OS の上で動くアプリケーションの処理は大きく 2 種類に分かれる。

  • OS へのリクエスト (System Call)
    • Input / Output
    • Memory 確保、解放
    • ...
  • CPU と Memory を直接つかった計算 (User Land 上の処理)
    • 四則演算
    • 確保済みの Memory へのアクセス (Read / Write)
    • ...

WebAssembly の方針

WebAssembly とは、多くのコンピューターで動作する事を目指した VM の仕様である。
しかし System Call を行うと、その処理は OS 依存となってしまいポータビリティーを下げる。

それを防ぐため、WebAssembly は User Land 上の処理しか出来ないように規格で定められている。
そして System Call が使えないという制限を補うために WebAssembly は他のプログラム言語から起動する事を前提としている。

WebAssembly の使い方としては、下記の 2 通りになる。

  • WebAssembly では System Call を使わない部分のコードだけを記載、外部プログラムからライブラリの様に使用する
  • WebAssembly では外部プログラムの関数を import し、WebAssembly からその関数をライブラリの様に使用する

プログラマー目線の WebAssembly

WebAssembly のコードは「プログラム言語共通のクラス定義」の様な物だ。
外部プログラムから使用する際は、下記の様な特徴を持つ。

  • instance 化してから使う
  • 同じ WebAssembly のコードから複数の instance を作成する事が可能
  • 各 instance の property に(アクセス権が有れば)アクセス(read/write)可能
  • パブリックな関数(メソッド)を呼び出す事が可能

WebAssembly コードの準備

最初に、各言語から使用する WebAssembly のコードを用意する。
今回は WebAssembly Text Format という低レイヤーの言語を用いる。

wabt のインストール

github の README.md を読んでコンパイルする。
C++ のコンパイル環境が必要なので注意。

wat2wasm というコマンドがコンパイルされるので、必要に応じて Path を通しておくと良いだろう。

wasm ファイルの準備

wasm ファイルとは、WebAssembly の規格に沿ったバイナリファイルの事である。
最初に各種言語から動かして動作確認するための wasm ファイルを用意しよう。

テキストエディタで以下の様な内容のファイルを作成し、 /tmp/addition.wat という名前で保存する。

(module
  (func (export "addition") (param $a i64) (param $b i64) (result i64)
    local.get $a
    local.get $b
    i64.add))

上記は WebAssembly Text Format という wasm ファイルの低レイヤーのソースコードであり、addition という名前で関数を公開している。(public なメソッドを定義している。)
この関数 addition は引数として i64 (符号付き 64 bit 整数)を 2 個とり、1 個の i64 を返す。

この wat ファイルを以下の様に wat2wasm コマンドでコンパイルすると /tmp/addition.wasm というバイナリファイルが出来る。

$ wat2wasm -o /tmp/addition.wasm /tmp/addition.wat
$ ls /tmp/addition.wasm
/tmp/addition.wasm

Vanilla JS から実行

説明の都合上、本章は JavaScript に興味の無い人でも斜めに読んで欲しい。

Vanilla JS から実行する処理の概要

前述の様に WebAssembly は「プログラム言語共通のクラス定義」の様な物だ。
そのため(JavaScript に限らず多くの環境では)実際の使用は下記の様なフローになるだろう。
太字リンク は JavaScript のクラス名(コンストラクター名)である事を示す。リンク先は MDN Web Docs 。)

  1. addition.wasm から Module を作成
  2. Module オブジェクトから Instance を作成
  3. Instance オブジェクトで公開されている関数 addition を取得
  4. 取得した関数 addition を実行

Module とは「プログラム言語共通のクラス定義」である wasm ファイルを JavaScript で使用できる形にコンパイルした物だ。
(私たちは既に wat ファイルをコンパイルして wasm ファイルを作成した。ここでは、その wasm ファイルを再度コンパイルする事になる。)
つまり Module の各オブジェクトは直感的には JavaScript のクラスの様な物だ。

Instance の各オブジェクトは、直感的には wasm ファイルで定義されたクラスで作成されたインスタンスの様な物だ。

筆者の JavaScript 環境

  • nodejs v18.16.1

Vanilla JS のコード

nodejs から WebAssembly を実行するコードは、例えば下記の様になる。

// Build Module
const fs = require('fs');
const wasm = fs.readFileSync('/tmp/addition.wasm');
const mod = new WebAssembly.Module(wasm);

// Build Instance
const importObject = {};
const instance = new WebAssembly.Instance(mod, importObject);

// Call function addition
const addition = instance.exports.addition;
const answer = addition(BigInt(7), BigInt(4));
console.log(`7 + 4 = ${answer}`);

上記のファイルを call_wasm.js という名前で保存して実行すると、下記の様な結果を得る。

$ node --version
v18.16.1
$ node call_wasm.js
7 + 4 = 11

上記のコードを少しずつ紹介していく。
最初に以下の行に注目しよう。

// Build Module
const fs = require('fs');
const wasm = fs.readFileSync('/tmp/addition.wasm');
const mod = new WebAssembly.Module(wasm);

上記は wasm ファイルを読み込み、Module のオブジェクト mod を作成している。
(wasm ファイルを JavaScript 用にコンパイルしている。)

続いて、次の行を読んでみる。

// Build Instance
const importObject = {};
const instance = new WebAssembly.Instance(mod, importObject);

WebAssembly.InstanceInstance のオブジェクト(直感的には wasm のインスタンス)を作成するビルダー関数だ。

前述の様に、WebAssembly は外部プログラム(今回は call_wasm)から関数等を import 可能だ。
WebAssembly.InstanceModule と import する対象を引数にとる。

しかし私たちが書いた wat ファイルでは何も import していない。
なので importObject として {} を渡している。

個人的には let instance = new mod(importObject) の様に書ければ直感的で良いと思うのだが、残念な事にそこまでは出来ない様だ。

なお、ブラウザーの JavaScript で ModuleInstance を作成する際は WebAssembly.instantiateStreaming を使った方が良いかもしれない。

最後に以下の行を読んでみる。

// Call function addition
const addition = instance.exports.addition;
const answer = addition(BigInt(7), BigInt(4));
console.log(`7 + 4 = ${answer}`);

Instance が公開している物は、全て exports というプロパティーに入っている。
私たちの書いた wat ファイルでは addition という名前で i64 (符号付き 64 bit 整数)を 2 個とり、 i64 を 1 個返す関数を公開している。
筆者の nodejs 環境では exports を見ると、下記の様になっていた。
[Function: 0] が気になる人は 前回の記事 をご参照ください。)

> instance.exports
[Object: null prototype] { addition: [Function: 0] }

この関数 addition の実行結果を answer という変数に保存する。
JavaScript から WebAssembly へ i64 の受け渡しをする際は BigInt を用いる。
(BigInt の下位 64 bit のみが渡される。桁あふれ注意。)
answer の型も BigInt になる。

wasmtime から実行

本章は wasmtime を使うにあたって Rust, Python, Ruby で共通の説明である。

wasmtime は JavaScript のグローバルオブジェクト WebAssembly を拡張したような構造をしている。
wasmtime を使用する場合、どの言語でも大体下記の様なフローになるだろう。
太字リンク は wasmtime で定義されたクラス名である事を示し、「作成」とはそのクラスのインスタンス作成の事である。リンク先は便宜上 Rust 用の公式ドキュメントを指している。)

  1. Engine を作成
  2. addition.wasm から Module を作成
  3. Store を作成
  4. Instance を作成
  5. Instance のインスタンスから公開されている関数 addition を取得
  6. 取得した関数 addition を実行

Engine は wasm ファイルを該当言語で使用可能な状態にするコンパイラーの様な物だ。
Engine のインスタンスには「クラッシュ時の挙動」、「非同期機能をサポート有無」等の設定が出来る。
(JavaScript ではこのような設定は出来ないので、このようなクラスは存在しなかった。)

Module は JavaScript の Module とほぼ同じだ。
直感的には Module の各インスタンスは wasm ファイルで定義されたクラスの様にふるまう。

説明の順番が前後するが、 Instance は JavaScript の Instance とほぼ同じだ。
直感的には Instance の各インスタンスは wasm ファイルで定義されたクラスのインスタンスの様にふるまう。

最後に Store だが、これに相当する物は JavaScript には存在しない。
Store の存在理由は、例えば下記であると筆者は考えている。

  • wasmtime のコア部分を実装している Rust では、JavaScript と違って Garbage Collection が存在しない。複数の Instance インスタンスに関わるメタプログラミングの様な事をする可能性を考えると、適切にメモリ管理をするためには Instance の各インスタンスが使用するデータのコンテナクラスが有った方が良い。
  • WebAssembly コード実行時の hook 設定等、WebAssembly のコードを制御する外部プログラムのコードを書く余地を残しておくべき。(今後の拡張にも有用である。)

難しければ、とりあえず「wasmtime を使うには Store という『おまじない』が必要」と覚えておけばよい。

なお、「 Store インスタンス」と「 Instance インスタンス」の関係は 1 対 多 である。
メモリ効率を無視すれば、プログラム全体で 1 個の Store をグローバル変数として使用する事が可能だ。

以降の章では wasmtime を使って Rust から実行, Python から実行, Ruby から実行 方法について、それぞれ説明している。
不要な章は読み飛ばしても大丈夫だ。

Rust から実行

筆者の Rust 環境

  • cargo 1.70.0
  • rustup 1.26.0
  • rustc 1.70.0 stable-x86_64-unknown-linux-gnu

Rust のコード

下記の様に call_addition というプロジェクトを作成する。

$ cargo new call_addition
$ cd call_addition

Cargo.toml の [dependencies] セクションに wasmtime をの設定を加える。
筆者の環境では Cargo.toml は下記の様になった。

[package]
name = "call_addition"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
wasmtime = "10.0.1"

Rust から WebAssembly を実行するコードは、例えば下記の様になる。

use std::fs;
use wasmtime::*;

fn main() -> wasmtime::Result<()> {
    // Build `Engine`.
    let engine = Engine::default();

    // Build `Module`.
    let wasm = fs::read("/tmp/addition.wasm").unwrap();
    let module = Module::new(&engine, wasm)?;

    // Build `Store`.
    let mut store = Store::new(&engine, ());

    // Build `Instance`
    let instance = Instance::new(&mut store, &module, &[])?;

    // Acquire function exported as `addition`
    let addition = instance.get_typed_func::<(i64, i64), i64>(&mut store, "addition")?;

    // Call addition
    let answer = addition.call(&mut store, (7, 4))?;
    println!("7 + 4 = {}", &answer);

    Ok(())
}

上記の内容で src/main.rs を上書きして実行すると、実行結果は以下の様になる

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.04s
     Running `target/debug/call_addition`
7 + 4 = 11

上記の Rust コードを少しずつ解説していく。
まずはコードの全体像を把握しよう。

use std::fs;
use wasmtime::*;

fn main() -> wasmtime::Result<()> {
...
}

見ての通り、本コードは main 関数が定義してあるだけである。
以下、main 関数の中身を上から少しずつ読んでみる。

    // Build `Engine`.
    let engine = Engine::default();

最初に Engine のインスタンスを作成している。
そんなに難しい事をするわけでは無いので、今回はデフォルトで作成する。

次に以下の行を読んでみる。

    // Build `Module`.
    let wasm = fs::read("/tmp/addition.wasm").unwrap();
    let module = Module::new(&engine, wasm)?;

ここでは Module を作成している。
やっている事は wasm ファイルを Rust 用にコンパイルしているのだが、そのコンパイラーとして先ほど作成した Engine インスタンスを使用している。

続いて以下の行を読んでみる。

    // Build `Store`.
    let mut store = Store::new(&engine, ());

ここでは Store を作成している。
2 個目の引数は、この Store に持たせるデータだ。
特殊な事をする時に使用する事も出来るのだが、今回の様に単純に WebAssembly を実行するだけならば使わない。
なので Rust で「空のデータ」を意味する () を渡した。

続けて下記の行を読んでみる。

    // Build `Instance`
    let instance = Instance::new(&mut store, &module, &[])?;

ここでは Instance のインスタンスを作成している。
前述の様に、WebAssembly は外部プログラム(今回は Rust)から関数等を import 可能だ。
関数 Instance::new の 3 個目の引数は、その import する対象への参照一覧だ。

しかし私たちが書いた wat ファイルでは何も import していない。
なので &[] を渡している。

次に以下の行を読んでみる。

    // Acquire function exported as `addition`
    let addition = instance.get_typed_func::<(i64, i64), i64>(&mut store, "addition")?;

Instance のインスタンスが出来たので、ここでは関数 addition を取得している。
WebAssembly としては名前さえ指定すれば exports している物は取得できるようになっているのだが、Rust は静的型付けの言語だ。
プログラマーは、今回取得しようとするものが関数である事、その引数が i64 型 2 個である事、戻り値が i64 1 個である事を(外部プログラムの) Rust コードに教える必要が有る。
そのために get_typed_func というメソッドを使用し、その引数と戻り値の型を <(i64, i64), i64> で指定している。

蛇足だが WebAssembly はが公開出来るのは関数、抽象ポインターテーブル、線形メモリ、グローバル変数のいずれかだ。
addition という名前で公開されている物を取得するには型情報を知る必要は無い。
しかし取得した後で addition が関数である事や、その引数や戻り値の型情報を得る事が出来る。
詳細は 前回の記事 を参照して欲しい。

最後に以下の行を読んでみる。

    // Call addition
    let answer = addition.call(&mut store, (7, 4))?;
    println!("7 + 4 = {}", &answer);

関数 addition が取得出来たら後は実行するだけだ。
注意点としてはメソッド addition.call の引数の取り方くらいだと思う。

引数の 1 個目は Store への参照であり、『おまじない』である。
2 個目の引数に、wasm ファイルの addition の取る引数をまとめて渡している。

Python から実行

筆者の Python 環境

  • Python 3.11.2
  • pip 23.0.1

Python のコード

最初に Python の version と環境に応じた方法で wasmtime をインストールする。

$ pip install wasmtime
Collecting wasmtime
  Downloading wasmtime-9.0.0-py3-none-manylinux1_x86_64.whl (6.6 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.6/6.6 MB 2.1 MB/s eta 0:00:00
Installing collected packages: wasmtime
Successfully installed wasmtime-9.0.0

Python から WebAssembly を実行するコードは、例えば下記の様になる。

from wasmtime import Engine, Module, Store, Instance

# Build Engine
engine = Engine()

# Build Module
wasm = open('/tmp/addition.wasm', 'rb').read()
module = Module(engine, wasm)

# Build Store
store = Store(engine)

# Build Instance
instance = Instance(store, module, ())

# Acquire function exported as addition
addition = instance.exports(store)['addition']

# Call addition
answer = addition(store, 7, 4)
print("7 + 4 = %d" % answer)

ほとんど Rust のコードと同じであり説明の必要も無いかもしれないが、一応上から説明していく。
以下では 太字のリンク は wasmtime で定義されたクラスである事を示す。リンク先は Python 版 wasmtime の公式ドキュメント である。

最初に以下の行を読んでみる。

# Build Engine
engine = Engine()

ここでは Engine のインスタンスを作成している。
今回はそんなに難しい事をするわけでは無いのでデフォルト引数で作成する。

次に以下の行を読んでみる。

# Build Module
wasm = open('/tmp/addition.wasm', 'rb').read()
module = Module(engine, wasm)

ここでは Module を作成している。
やっている事は wasm ファイルを Python 用にコンパイルしているのだが、そのコンパイラーとして先ほど作成した Engine インスタンスを使用している。

続いて以下の行を読んでみる。

# Build Store
store = Store(engine)

ここでは Store のインスタンスを作成している。

Rust の説明でも記載したが、 Store() の引数 data を使う事で Store のインスタンスには任意のデータを持たせる事が出来る。
今回の様に特殊な事はせず wasm ファイルを使うだけであれば、このデータは必要ない。
引数を省略する事でデフォルトの None を渡している。

ところで 公式ドキュメント を読めば分かるが、デフォルトの Engine を使用する場合は引数の engine を省略できる。
しかし、Store のプロパティーの engine と、対応する Module() の引数に渡す engine はアドレスが一致している必要が有るので注意して欲しい。

つまり ModuleStore のインスタンス作成は下記方法でも行う事が可能だ。
Engine を明示的に作成する必要が無いので、コードが 1 ステップ減る。)

# Build Store
store = Store()

# Build Module
wasm = open('/tmp/addition.wasm', 'rb').read()
module = Module(store.engine, wasm)

しかし以下の方法で行うと Store()Module() がそれぞれ別の Engine を作成するのでアドレスが異なってしまう。
後で Instance 作成時にエラーになってしまう。

# Build Store
store = Store()

# Build Module
wasm = open('/tmp/addition.wasm', 'rb').read()
module = Module(Engine(), wasm)

Python の柔軟であるが故に発生しやすいバグだと思う。

次に以下の行を読んでみる。

# Build Instance
instance = Instance(store, module, ())

ここでは Instance を作成している。

前述の様に、WebAssembly は外部プログラム(今回は Python)から関数等を import 可能だ。
関数 Instance() の 3 個目の引数は、その import する対象への参照だ。

しかし私たちが書いた wat ファイルでは何も import していない。
なので () を渡している。

続いて以下の行を読んでみる。

# Acquire function exported as addition
addition = instance.exports(store)['addition']

ここでは、関数 addition を取得している。
Python は JavaScript の様に動的型付け言語なので、名前を指定するだけで取得可能だ。
(Rust と異なり、コードに型情報を教える必要は無い。)

最後に以下の行を読んでみる。

# Call addition
answer = addition(store, 7, 4)
print("7 + 4 = %d" % answer)

ここでは関数 addition を実行している。
addition() には最初の引数に『おまじない』として store を渡しているが、それ以降は wasm の addition に渡す引数である。

なお Python は動的型付け言語だが WebAssembly は静的型付けを行う。
Python の addition() 経由で渡す引数の数や型情報が異なると、ちゃんと例外を投げてくれる。

Ruby から実行

筆者の Ruby 環境

  • ruby 3.1.2p20

Ruby のコード

最初に gem で wasmtime をインストールする。
(必要に応じて bundler 等を使うと良いだろう。)

gem install wasmtime
Fetching wasmtime-9.0.1-x86_64-linux.gem
Successfully installed wasmtime-9.0.1-x86_64-linux
Parsing documentation for wasmtime-9.0.1-x86_64-linux
Installing ri documentation for wasmtime-9.0.1-x86_64-linux
Done installing documentation for wasmtime after 0 seconds
1 gem installed

Ruby から WebAssembly を実行するコードは、例えば下記の様になる。

require 'wasmtime'

# Build Engine
engine = Wasmtime::Engine.new

# Build Module
wasm = open('/tmp/addition.wasm', 'rb').read
mod = Wasmtime::Module.new(engine, wasm)

# Build Store
store = Wasmtime::Store.new(engine)

# Build instance
instance = Wasmtime::Instance.new(store, mod)

# Acquire function exported as 'addition'
addition = instance.export('addition').to_func

# Call function addition
answer = addition.call(7, 4)
puts("7 + 4 = %d" % answer)

Python コードのコピペと見間違える様なコードだが、一応説明していく。
以下では 太字のリンク は wasmtime で定義されたクラスである事を示す。リンク先は Ruby 版 wasmtime の公式ドキュメント である。

最初に以下の行を読んでみる。

require 'wasmtime'

# Build Engine
engine = Wasmtime::Engine.new

ここでは Engine のインスタンスを作成している。
今回はそんなに難しい事をするわけでは無いのでデフォルト引数で作成する。

続いて以下の行を読んでみる。

# Build Module
wasm = open('/tmp/addition.wasm', 'rb').read
mod = Wasmtime::Module.new(engine, wasm)

ここでは Module を作成している。
やっている事は wasm ファイルを Ruby 用にコンパイルしているのだが、そのコンパイラーとして先ほど作成した Engine インスタンスを使用している。

続いて以下の行を読んでみる。

# Build Store
store = Wasmtime::Store.new(engine)

ここでは Store のインスタンスを作成している。

Rust の説明でも記載したが、 Store.new の引数 data を使う事で Store のインスタンスには任意のデータを持たせる事が出来る。
今回の様に特殊な事はせず wasm ファイルを使うだけであれば、このデータは必要ない。
引数を省略する事でデフォルトの nil を渡している。

ところで、 Store のドキュメントを読むと Ruby には WasiCtxBuilder というクラスが有る事に気が付く。
(執筆時点では wasmtime の Rust や Python 版のドキュメントに、このようなクラスは見つからなかった。)
これが何なのか筆者も良く分からないが、今後 wasmtime が拡張する予定なのかもしれない。
現段階では省略する事でデフォルトの nil を渡している。

続いて以下の行を読んでみる。

# Build instance
instance = Wasmtime::Instance.new(store, mod)

ここでは Instance を作成している。

前述の様に、WebAssembly は外部プログラム(今回は Ruby)から関数等を import 可能だ。
関数 Instance.new は 3 個目の引数で、その import する対象への参照を受け取る事が出来る。

しかし私たちが書いた wat ファイルでは何も import していない。
なので省略してデフォルト引数の [] を渡している。

続いて以下の行を読んでみる。

# Acquire function exported as 'addition'
addition = instance.export('addition').to_func

ここでは関数 addition を取得している。
Ruby の場合は .to_funcFunc に明示的に型変換を行う必要がある。
WebAssembly が export する可能性が有るのは関数だけでは無いからだ。
(Python や JavaScript では、この様な型変換は必要ない。)

Ruby は動的型付け言語だが、型にはうるさい。
Ruby の文化を良く反映したコードだと思う。

最後に以下の行を読んでみる。

# Call function addition
answer = addition.call(7, 4)
puts("7 + 4 = %d" % answer)

ここでは取得した関数 addition を実行している。
他の言語とは異なり、 addition.call には『おまじない』の store を渡していない。

なお Ruby は動的型付け言語だが WebAssembly は静的型付けを行う。
Ruby の addition.call 経由で渡す引数の数や型情報が異なると、ちゃんと例外を投げてくれる。

まとめ

筆者は開発ツール等を使って手を抜く事は好きなのだが、ツールに「自分の理解できない何か」を勝手にやられる事が好きではない。(この考えに共感してくれるプログラマーは少なくないと思う。)
なので、「開発ツールが何をやっているか」を知るために少し低レイヤーの事を調べてみた。

以前書いた WebAssembly のメモリ構造 という記事を合わせて読むと本記事の理解も深まると思う。
興味のある人は、そちらも是非読んで欲しい。

17
16
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
16