elm-rust-binding

Crates.ioelm-rust-binding
lib.rselm-rust-binding
version0.2.0
sourcesrc
created_at2024-11-18 20:28:23.691153
updated_at2024-11-19 09:34:22.626792
descriptionCall Elm functions from Rust in an ergonomic way
homepagehttps://github.com/anmolitor/elm-rust-binding
repositoryhttps://github.com/anmolitor/elm-rust-binding
max_upload_size
id1452629
size25,489
Andreas Molitor (anmolitor)

documentation

README

Elm Rust Binding

This crate offers a way to conveniently call an Elm function from Rust. The main motivation here is for testing purposes:

  • You have some logic in your Elm frontend that cannot be moved to the backend (because e.g. it is called in a hot loop)
  • You have related logic in your Rust backend

Then you can call your Elm code in a Rust test with this crate to verify the two implementations are in sync. The performance this crate offers is NOT optimal - do not use this for Interop in production code.

Example

Rust side

use elm_rust_binding::{ElmRoot, Result};

#[test]
fn call_elm() -> Result<()> {
    let mut elm_root = ElmRoot::new("../frontend/src")?;
    let mut square_fn = elm_root.prepare("Math.square")?;
    let squared = square_fn.call(4)?;
    assert_eq!(squared, 16);
    Ok(())
}

Elm side

-- In /frontend/src/Math.elm
module Math exposing (square)


square : Int -> Int
square n = n * n

Note that you can call the function multiple times, which will improve performance over creating a new ElmRoot and ElmFunctionHandle. This is especially useful for fuzz/property-based testing

Implementation Details

How does this work under the hood? It essentially boils down to 6 steps:

  1. Infer the Elm input and output types based on the Rust type annotations (i32 -> Int, bool -> Bool, etc.).
  2. Generate an .elm file with a Platform.worker main function. It will accept the input type over its flags and return the output type via a port. Internally it will call the specified function by importing it.
  3. Invoke the Elm compiler on the generated file, producing a .js file.
  4. Make the produced .js file ESM-compatible.
  5. Load the module into a Deno runtime.
  6. Initialize the Elm application every time .call is invoked by passing the input argument as flags and getting the output via .subscribe.

The generated files are removed automatically and are postfixed with a UUID to prevent different invocations messing with each other. If you call .debug() on the ElmRoot, the files are not removed to help debugging issues.

Commit count: 11

cargo fmt