Hello world (Component Model)
In Hello world (WASI) we built a Wasm component with WASI APIs and ran it on Wasmtime.
This tutorial composes Scala with a Rust guest built for wasm32-wasip2: Rust exports a custom greeter API, and Scala imports it, and wac plugs the two .wasm artifacts into one runnable component.
Complete project available at component-examples/rust-compose.
Prerequisites
Installation, plus a Rust toolchain with the wasm32-wasip2 target (rustup target add wasm32-wasip2).
Project layout
The reference lives under component-examples/rust-compose (the repo also defines rustComposeScala in its root build.sbt):
rust-compose/
wit/
world.wit
scala/
build.sbt
src/main/scala/rustcompose/
RunImpl.scala
rust-greeter/
Cargo.toml
src/
lib.rs # wit-bindgen reads ../wit
WIT
First, write a WIT (Wasm Interface Type) file shared by the Scala and Rust. For more details about WIT, please refer https://component-model.bytecodealliance.org/design/wit.html.
In interface greeter, define greet function that takes a string and returns a string. That is the API the two languages call across the component boundary.
Next, declare the world each component implements. In world scala, import greeter, and in world rust, export greeter. wac plug will compose Rust’s export to Scala’s import.
package scala-wasm:rust-compose;
interface greeter {
greet: func(name: string) -> string;
}
world scala {
import greeter;
}
world rust {
export greeter;
}
Run wkg wit fetch from the project directory to download the WIT files this world depends on (into wit/deps/).
Scala component
Implement the Scala side first, calls greet and prints the result.
Similar to Hello world (WASI), WasmComponentModuleInitializerExport exports main as wasi:cli/run@0.2.12.
import org.scalajs.ir.WitScope
import org.scalajs.jsenv.wasmtime.WasmtimeEnv
import org.scalajs.linker.interface.ESVersion
import org.scalajs.linker.interface.WasmComponentModuleInitializerExport
import org.scalajs.linker.interface.WasmComponentModuleInitializerExport._
lazy val root = project
.in(file("."))
.enablePlugins(ScalaJSPlugin)
.settings(
name := "rust-compose-scala",
scalaVersion := "2.13.18",
libraryDependencies +=
"io.github.scala-wasm" %%% "wasi4s" % "0.0.1+wasi-0.2.12",
scalaJSWitDirectory := baseDirectory.value / "../wit",
scalaJSWitWorld := Some("scala"),
scalaJSWitPackage := Some("rustcompose"),
scalaJSLinkerConfig ~= {
_.withESFeatures(_.withUseWebAssembly(true).withESVersion(ESVersion.ES2022))
.withModuleKind(ModuleKind.WasmComponent)
.withWasmFeatures(_.withModuleInitializerExport(Some(
WasmComponentModuleInitializerExport(
scope = WitScope.Interface("wasi", "cli", "run", Some("0.2.12")),
functionName = "run",
resultType = ResultType.ResultUnitUnit,
))))
},
scalaJSUseMainModuleInitializer := true,
jsEnv := new WasmtimeEnv(),
)
scalaJSWitDirectory, scalaJSWitWorld, and scalaJSWitPackage point at ../wit and world scala. On compile, the plugin runs wit-bindgen-scala (bindings under managed sources) and passes WIT settings to the linker.
src/main/scala/rustcompose/RunImpl.scala:
package rustcompose
import io.github.scalawasm.wasi4s.wasi.cli.stdout._
import rustcompose.scala_wasm.rust_compose.greeter.greet
import scala.scalajs.wit.unsigned.UByte
object RunImpl {
def main(args: Array[String]): Unit =
println(greet("Scala"))
private def println(s: String): Unit = {
val out = getStdout()
try {
val bytes = (s + "\n").getBytes().asInstanceOf[Array[UByte]]
out.blockingWriteAndFlush(bytes).getOrElse(
throw new RuntimeException("Failed to write to stdout"))
} finally {
out.close()
}
}
}
Rust greeter
Next, implement the Rust component. We woundn't explain details, but wit-bindgen reads ../wit and generates bindings for world rust. And we implement greeter on the generated Guest trait. Pass the incoming name to ferris-says and return the formatted string.
rust-greeter/src/lib.rs:
#![allow(unused)] fn main() { mod bindings { wit_bindgen::generate!({ path: "../wit", world: "rust", }); use super::Component; export!(Component); } use bindings::exports::scala_wasm::rust_compose::greeter::Guest; struct Component; impl Guest for Component { fn greet(name: String) -> String { let mut buffer = Vec::new(); let message = format!("Hello from Rust, {name}!"); ferris_says::say(&message, 80, &mut buffer).unwrap(); String::from_utf8(buffer).unwrap() } } }
Build and run
cd scala
sbt fastLinkJS
cd rust-greeter
cargo build --target wasm32-wasip2 --release
Plug Rust into Scala, then run with Wasmtime:
cd rust-compose
wac plug --plug rust-greeter/target/wasm32-wasip2/release/rust_greeter.wasm \
../target/path/to/main.wasm \
-o out.wasm
wasmtime -W gc,function-references,exceptions out.wasm
_________________________
< Hello from Rust, Scala! >
-------------------------
\
\
_~^~^~_
\) / o o \ (/
'_ - _'
/ '-----' \