DEV Community

Cover image for Rust WebAssembly with WebForms Core 2.1
Elanat Framework
Elanat Framework

Posted on

Rust WebAssembly with WebForms Core 2.1

What is WebForms Core?

WebForms Core is a modern multi-platform web technology from Elanat for building interactive HTML applications through server-defined commands and client-side command execution.

WebForms Core uses a server-oriented architecture in which the server defines UI behavior through commands, while a lightweight browser runtime executes those commands.

With WebForms Core 2.1 (WFC), WebAssembly can also be used as an execution layer within this architecture.

In this article, we will use Rust WebAssembly with WebForms Core and demonstrate two approaches:

  • Rust with wasm-bindgen
  • Raw WebAssembly using extern "C"

Both approaches can execute Rust code through WebForms Core.

More importantly, Rust code running inside WebAssembly can use the WebForms Core WebForms class to generate WebForms Core commands.

This creates a pipeline such as:

Rust
  ↓
WebAssembly
  ↓
WebForms
  ↓
WebForms Core Commands
  ↓
WebFormsJS
  ↓
Browser DOM
Enter fullscreen mode Exit fullscreen mode

The WASM module executes in the browser, while the server-side WebForms class declares when and how the WASM method should be executed. The WASM method can then use the WebForms API to generate WebForms Core commands, which are executed by WebFormsJS in the browser.


Rust WebAssembly

Rust and WebForms Core

Rust has first-class support for compiling code to WebAssembly.

A Rust project can target:

wasm32-unknown-unknown
Enter fullscreen mode Exit fullscreen mode

For example:

cargo +stable-x86_64-pc-windows-gnu build --release --target wasm32-unknown-unknown
Enter fullscreen mode Exit fullscreen mode

The resulting WebAssembly module is generated under:

target/wasm32-unknown-unknown/release/
Enter fullscreen mode Exit fullscreen mode

For example:

webformscore_wasm_test.wasm
Enter fullscreen mode Exit fullscreen mode

This module can then be executed in the browser through WebForms Core's WebAssembly execution layer.


Rust in Crates.io

Rust in Crates.io (https://crates.io/crates/webformscore)

CLI

cargo add webformscore
Enter fullscreen mode Exit fullscreen mode

Project settings

[dependencies]
webformscore = "#.#.#"
Enter fullscreen mode Exit fullscreen mode

Two Rust WebAssembly Approaches

WebForms Core can work with different Rust WebAssembly execution models.

1. wasm-bindgen

The first approach uses:

use wasm_bindgen::prelude::*;
Enter fullscreen mode Exit fullscreen mode

and:

#[wasm_bindgen]
Enter fullscreen mode Exit fullscreen mode

The Rust project can then be processed by wasm-bindgen:

wasm-bindgen target/wasm32-unknown-unknown/release/webformscore_wasm_test.wasm --target web --out-dir pkg
Enter fullscreen mode Exit fullscreen mode

This produces JavaScript glue code and a WebAssembly module.

The resulting files include:

webformscore_wasm_test.js
webformscore_wasm_test_bg.wasm
Enter fullscreen mode Exit fullscreen mode

WebForms Core can execute the generated JavaScript module through its JavaScript WASM executor.

The wasm-bindgen approach is useful when Rust code needs convenient JavaScript interoperability.


2. Raw WebAssembly

The second approach does not use wasm-bindgen.

Instead, Rust functions can be exported directly using:

#[no_mangle]
pub extern "C" fn
Enter fullscreen mode Exit fullscreen mode

For example:

#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
    a + b
}
Enter fullscreen mode Exit fullscreen mode

The function is exported directly into the WebAssembly module.

There is no JavaScript glue code generated by wasm-bindgen.

The resulting architecture is:

Rust
  ↓
extern "C"
  ↓
Raw .wasm
  ↓
WebForms Core WASM Executor
Enter fullscreen mode Exit fullscreen mode

This approach is particularly interesting for WebForms Core because the WASM module can expose a direct callable interface without depending on the wasm-bindgen JavaScript layer.


Creating the Rust Project

Create a Rust library project:

cargo new webformscore_wasm_test --lib
Enter fullscreen mode Exit fullscreen mode

Enter the project:

cd webformscore_wasm_test
Enter fullscreen mode Exit fullscreen mode

The project can use:

[package]
name = "webformscore_wasm_test"
version = "2.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
webformscore = "2.1.0"
Enter fullscreen mode Exit fullscreen mode

The important dependency is:

webformscore = "2.1.0"
Enter fullscreen mode Exit fullscreen mode

This makes the WebForms Core Rust implementation available inside the WebAssembly project.


Using WebForms Core from Rust

The Rust code can directly use the WebForms class:

use webformscore::WebForms;
Enter fullscreen mode Exit fullscreen mode

For example:

let mut form = WebForms::new();

form.set_text("h3Tag", "Text From Wasm");
form.set_background_color("-", "lightgreen");
form.set_font_size("-", "30px");

form.response()
Enter fullscreen mode Exit fullscreen mode

The Rust code does not directly manipulate the browser DOM.

Instead, it creates a WebForms Core response containing commands.

This preserves the same command-oriented architecture used by WebForms Core.

The Rust WASM module therefore does not need to know how WebFormsJS applies the commands to the DOM.


Rust with wasm-bindgen

The wasm-bindgen version is straightforward.

The complete Rust source can be written as:

use wasm_bindgen::prelude::*;
use webformscore::WebForms;

#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[wasm_bindgen]
pub fn set_data(
    input_place: String,
    text: String,
    background_color: String,
    font_size: String,
) -> String {
    let mut form = WebForms::new();

    form.set_text(&input_place, &text);
    form.set_background_color("-", &background_color);
    form.set_font_size("-", &font_size);

    form.response()
}

#[wasm_bindgen]
pub fn get_html() -> String {
    "<marquee>Tag From Wasm!</marquee>".to_string()
}
Enter fullscreen mode Exit fullscreen mode

The important point is that the Rust methods can use:

WebForms
Enter fullscreen mode Exit fullscreen mode

directly.

The WebAssembly method therefore does not need to manipulate the DOM itself.

It can generate a WebForms Core response instead.


Building the wasm-bindgen Module

Build the Rust WebAssembly module:

cargo +stable-x86_64-pc-windows-gnu build --release --target wasm32-unknown-unknown
Enter fullscreen mode Exit fullscreen mode

Then run:

wasm-bindgen target\wasm32-unknown-unknown\release\webformscore_wasm_test.wasm --target web --out-dir pkg
Enter fullscreen mode Exit fullscreen mode

The generated package contains the JavaScript glue required by the wasm-bindgen execution model.

The resulting structure is conceptually:

webformscore_wasm_test.js
webformscore_wasm_test_bg.wasm
webformscore_wasm_test.d.ts
Enter fullscreen mode Exit fullscreen mode

The JavaScript file provides the JavaScript-facing interface for the Rust WebAssembly module.


Raw WebAssembly with Rust

Rust can also export functions directly without wasm-bindgen.

A simple function is:

#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
    a + b
}
Enter fullscreen mode Exit fullscreen mode

This function is exported directly by the WebAssembly module.

A complete Raw WASM example can be written as:

use std::ffi::CStr;
use webformscore::WebForms;

#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[no_mangle]
pub extern "C" fn alloc(size: usize) -> *mut u8 {
    let mut buffer = Vec::with_capacity(size);
    let ptr = buffer.as_mut_ptr();

    std::mem::forget(buffer);

    ptr
}

#[no_mangle]
pub extern "C" fn set_data(
    input_place: *const u8,
    text: *const u8,
    background_color: *const u8,
    font_size: *const u8,
) -> *const u8 {
    let input_place = unsafe {
        CStr::from_ptr(input_place as *const i8)
            .to_str()
            .unwrap()
    };

    let text = unsafe {
        CStr::from_ptr(text as *const i8)
            .to_str()
            .unwrap()
    };

    let background_color = unsafe {
        CStr::from_ptr(background_color as *const i8)
            .to_str()
            .unwrap()
    };

    let font_size = unsafe {
        CStr::from_ptr(font_size as *const i8)
            .to_str()
            .unwrap()
    };

    let mut form = WebForms::new();

    form.set_text(input_place, text);
    form.set_background_color("-", background_color);
    form.set_font_size("-", font_size);

    let response = form.response();

    let mut output = response.into_bytes();
    output.push(0);

    Box::into_raw(output.into_boxed_slice()) as *const u8
}

#[no_mangle]
pub extern "C" fn get_html() -> *const u8 {
    b"<marquee>Tag From Wasm!</marquee>\0".as_ptr()
}
Enter fullscreen mode Exit fullscreen mode

This version has no:

use wasm_bindgen::prelude::*;
Enter fullscreen mode Exit fullscreen mode

and no:

#[wasm_bindgen]
Enter fullscreen mode Exit fullscreen mode

The functions are exported directly from the WebAssembly module.


Passing Strings to Raw WebAssembly

Primitive values such as:

i32
Enter fullscreen mode Exit fullscreen mode

are straightforward:

pub extern "C" fn add(a: i32, b: i32) -> i32
Enter fullscreen mode Exit fullscreen mode

Strings require an explicit memory representation.

In the Raw WASM example, WebForms Core passes strings into WebAssembly memory and the Rust code reads them using:

CStr::from_ptr(...)
Enter fullscreen mode Exit fullscreen mode

For example:

let text = unsafe {
    CStr::from_ptr(text as *const i8)
        .to_str()
        .unwrap()
};
Enter fullscreen mode Exit fullscreen mode

The Rust method can then use the resulting string normally:

form.set_text(input_place, text);
Enter fullscreen mode Exit fullscreen mode

The response is returned through a pointer to UTF-8 data.

This represents a simple ABI suitable for direct WebAssembly execution.


Returning WebForms Core Responses

The most interesting part of the Raw WASM example is that set_data does not return a traditional application value.

It creates a WebForms Core response:

let mut form = WebForms::new();

form.set_text(input_place, text);
form.set_background_color("-", background_color);
form.set_font_size("-", font_size);

let response = form.response();
Enter fullscreen mode Exit fullscreen mode

The response can then be returned to WebForms Core.

Therefore:

Rust WASM
    ↓
WebForms
    ↓
response()
    ↓
WebForms Core
    ↓
WebFormsJS
    ↓
Browser DOM
Enter fullscreen mode Exit fullscreen mode

The Rust WebAssembly method effectively becomes another source of WebForms Core commands.

This is the central architectural idea of the example.


Using Rust WASM from WebForms Core

The server-side WebForms Core controller can declare a WASM method invocation that is executed by the browser.

For example:

using CodeBehind;

public partial class WasmRustController : CodeBehindController
{
    public void PageLoad(HttpContext context)
    {
        string WasmPath = "/web-assembly/rust/webformscore_wasm_test_bg.wasm";

        WebForms form = new WebForms();

        form.AddText(
            "<b>",
            Fetch.WasmMethod(
                WasmLanguage.Rust,
                WasmPath,
                "add",
                [10000, 3]
            )
        );

        form.SetWasmEvent(
            "WasmEvent",
            HtmlEvent.OnClick,
            WasmLanguage.Rust,
            WasmPath,
            "setData",
            ["h3Tag", "Text From Wasm", "lightgreen", "30px"]
        );

        form.SetWasmEvent(
            "WasmEventWithOutput",
            HtmlEvent.OnClick,
            WasmLanguage.Rust,
            WasmPath,
            "getHtml",
            [],
            "WasmHtmlOutput"
        );

        Write(form.ExportToHtmlComment());
    }
}
Enter fullscreen mode Exit fullscreen mode

Both the wasm-bindgen and Raw WebAssembly examples use server-side WebForms Core code to demonstrate the power of its server-oriented architecture. However, server-side orchestration is not required by WebForms Core itself. These scenarios can also be configured to run without server-side WebForms code when server orchestration is not needed.

The same WFC API can therefore declare Rust WASM method calls.

The server does not need to know the internal implementation of the Rust method.

It only needs to specify:

WASM language
WASM path
method name
arguments
Enter fullscreen mode Exit fullscreen mode

The server defines the execution behavior, while the browser executes the WASM module and processes the resulting WebForms Core response.


Three Rust WASM Methods

The example exposes three methods:

add
set_data
get_html
Enter fullscreen mode Exit fullscreen mode

The first method returns a number:

add(10000, 3)
Enter fullscreen mode Exit fullscreen mode

which produces:

10003
Enter fullscreen mode Exit fullscreen mode

The second method receives strings and generates a WebForms Core response:

set_data(
    "h3Tag",
    "Text From Wasm",
    "lightgreen",
    "30px"
)
Enter fullscreen mode Exit fullscreen mode

The third method returns HTML:

get_html()
Enter fullscreen mode Exit fullscreen mode

which produces:

<marquee>Tag From Wasm!</marquee>
Enter fullscreen mode Exit fullscreen mode

This demonstrates that WebForms Core can use WASM methods for different kinds of operations:

  • returning values
  • generating WebForms Core commands
  • returning HTML

HTML

The HTML remains standard HTML:

@page
@controller WasmRustController
@layout "/layout.aspx"

@{
    ViewData.Add("title", "Rust Wasm");
}

<h3>Rust Wasm</h3>

<b>Rust WASM Result: </b>

<br>

<button id="WasmEvent">
    Wasm Event
</button>

<br>

<h3 id="h3Tag">
    Wasm Tag Changing!
</h3>

<button id="WasmEventWithOutput">
    Wasm Event With Output
</button>

<p id="WasmHtmlOutput">
    Wasm Html Output
</p>
Enter fullscreen mode Exit fullscreen mode

There is no custom WebAssembly HTML element.

There is no special component syntax.

There is no Rust-specific DOM layer.

The page remains standard HTML.


The WASM Method as a Command Execution Source

Consider this:

form.SetWasmEvent(
    "WasmEvent",
    HtmlEvent.OnClick,
    WasmLanguage.Rust,
    WasmPath,
    "setData",
    ["h3Tag", "Text From Wasm", "lightgreen", "30px"]
);
Enter fullscreen mode Exit fullscreen mode

WebForms Core declares that when the button is activated, a Rust WASM method should be executed.

The Rust method can then create:

let mut form = WebForms::new();

form.set_text(input_place, text);
form.set_background_color("-", background_color);
form.set_font_size("-", font_size);
Enter fullscreen mode Exit fullscreen mode

and return the WebForms Core response.

The WASM method therefore becomes an execution source for WebForms Core commands.

The browser does not need a separate Rust-specific UI architecture.


WASM Event With Output

The same mechanism can return HTML:

#[no_mangle]
pub extern "C" fn get_html() -> *const u8 {
    b"<marquee>Tag From Wasm!</marquee>\0".as_ptr()
}
Enter fullscreen mode Exit fullscreen mode

The server connects it to an output element:

form.SetWasmEvent(
    "WasmEventWithOutput",
    HtmlEvent.OnClick,
    WasmLanguage.Rust,
    WasmPath,
    "getHtml",
    [],
    "WasmHtmlOutput"
);
Enter fullscreen mode Exit fullscreen mode

The returned HTML is then placed into:

<p id="WasmHtmlOutput">
    Wasm Html Output
</p>
Enter fullscreen mode Exit fullscreen mode

This demonstrates that a Rust WASM method can also be used as a source of dynamic UI output.


One WebForms Core API, Different WASM Models

The WebForms Core API does not have to change when the WASM execution model changes.

The application can use:

Rust + wasm-bindgen
Enter fullscreen mode Exit fullscreen mode

or:

Rust + Raw WebAssembly
Enter fullscreen mode Exit fullscreen mode

while the WebForms Core invocation remains conceptually the same:

WasmMethod
Enter fullscreen mode Exit fullscreen mode

The execution layer handles the differences between the WASM models.

This gives WebForms Core a unified way to work with different WebAssembly environments.


wasm-bindgen vs Raw WebAssembly

The two approaches have different characteristics.

Feature wasm-bindgen Raw WebAssembly
Rust API High-level Low-level
String handling Mostly abstracted Explicit memory handling
JavaScript glue Generated No wasm-bindgen glue
.js module Yes No
Direct .wasm loading Possible with a compatible interface Yes
ABI complexity Mostly handled by wasm-bindgen Developer-defined
Memory/ABI control Mostly abstracted Developer-defined
WebForms Core integration Yes Yes
Best suited for General Rust WASM applications Direct WASM execution

The wasm-bindgen approach is convenient when Rust needs a rich JavaScript interoperability layer.

The Raw WebAssembly approach is attractive when the goal is a directly callable WASM module with a developer-defined ABI.

Neither approach changes the WebForms Core command model.


WebForms Core Rust WASM Architecture

The resulting architecture can be viewed as:

                         Server
                           │
                           │ WebForms
                           ↓
                     WasmMethod
                           │
                           ↓
                    Browser Runtime
                           │
                  ┌────────┴────────┐
                  │                 │
                  ↓                 ↓
           wasm-bindgen        Raw WASM
                  │                 │
                  ↓                 ↓
             Rust WASM          Rust WASM
                  │                 │
                  └────────┬────────┘
                           ↓
                       WebForms
                           │
                           ↓
                  WebForms Core Response
                           │
                           ↓
                       WebFormsJS
                           │
                           ↓
                      HTML DOM
Enter fullscreen mode Exit fullscreen mode

This shows the role of WebAssembly more clearly.

WebAssembly does not replace the WebForms Core execution model.

Instead, it provides another execution environment inside that model.


No Separate JavaScript Business Logic

One of the interesting properties of this example is that application behavior can be implemented in Rust without creating a separate JavaScript business-logic layer.

The Rust code contains the behavior:

let mut form = WebForms::new();

form.set_text(input_place, text);
form.set_background_color("-", background_color);
form.set_font_size("-", font_size);

form.response()
Enter fullscreen mode Exit fullscreen mode

The HTML contains standard HTML:

<button id="WasmEvent">
    Wasm Event
</button>
Enter fullscreen mode Exit fullscreen mode

And WebForms Core connects the two:

form.SetWasmEvent(
    "WasmEvent",
    HtmlEvent.OnClick,
    WasmLanguage.Rust,
    WasmPath,
    "setData",
    [...]
);
Enter fullscreen mode Exit fullscreen mode

The Rust code does not need to search for the button, register a DOM event listener, or manually update the target element.

WebForms Core defines the interaction, while WebFormsJS executes the resulting commands in the browser.


Raw WebAssembly and wasm-bindgen Are Different

It is important to distinguish these two approaches.

A Raw WebAssembly module can expose functions directly:

module.wasm
     ↓
WebAssembly.instantiate()
     ↓
exported function
Enter fullscreen mode Exit fullscreen mode

The wasm-bindgen approach introduces a generated JavaScript interoperability layer:

Rust
 ↓
wasm-bindgen
 ↓
generated JavaScript
 ↓
_bg.wasm
 ↓
Rust exported function
Enter fullscreen mode Exit fullscreen mode

These are different execution models.

WebForms Core can provide an abstraction over both models while keeping the application-level WASM invocation consistent.


WebAssembly as an Execution Layer

This example demonstrates a broader idea.

WebAssembly does not necessarily have to represent an entire frontend application.

A WASM module can instead provide a specific execution capability.

For example:

WebForms Core
      ↓
Rust WASM
      ↓
Process data
      ↓
Generate WebForms Core response
      ↓
WebFormsJS
      ↓
Browser DOM
Enter fullscreen mode Exit fullscreen mode

The Rust code can therefore perform computation, generate UI commands, or return values without becoming a complete client-side application framework.

This allows WebAssembly to participate in the existing WebForms Core command architecture.


Rust and WebForms Core

Rust is particularly interesting in this architecture because it can be compiled into WebAssembly while still using the WebForms Core API.

For example:

let mut form = WebForms::new();

form.set_text("h3Tag", "Text From Wasm");
Enter fullscreen mode Exit fullscreen mode

The Rust code does not need to know how WebFormsJS internally executes the resulting command.

It only creates the WebForms Core response.

This separates:

Application execution
Enter fullscreen mode Exit fullscreen mode

from:

Browser UI execution
Enter fullscreen mode Exit fullscreen mode

The WASM module performs its computation and creates the desired WebForms Core response, while WebFormsJS remains responsible for applying commands to the HTML DOM.


Rust WASM Compared with Direct JavaScript DOM Manipulation

A conventional JavaScript approach might directly perform:

document.getElementById("h3Tag").textContent = "Text From Wasm";
Enter fullscreen mode Exit fullscreen mode

The Rust WebAssembly approach demonstrated here does not need to manipulate the DOM directly.

Instead, it can produce WebForms Core behavior through:

WebForms
Enter fullscreen mode Exit fullscreen mode

This means the Rust code participates in the same command architecture used by the rest of the WebForms Core application.

The UI execution remains centralized in the WebForms Core command pipeline.


WebForms Core 2.1

WebForms Core 2.1 extends the WebForms Core execution model with WebAssembly integration.

Rust is particularly interesting for this capability because it supports both:

wasm-bindgen
Enter fullscreen mode Exit fullscreen mode

and:

Raw WebAssembly
Enter fullscreen mode Exit fullscreen mode

The same WebForms Core application can therefore use Rust WASM as an execution layer while keeping its HTML and UI command model intact.

The important point is not simply that:

Rust can run in the browser.

The more interesting part is:

Rust running in WebAssembly can participate in the WebForms Core command architecture.

A WASM module can therefore become a computational or UI-command-producing execution environment without requiring the application to adopt a separate frontend architecture.


Final Architecture

The complete architecture can be summarized as:

                    Server
                      │
                      ↓
                  WebForms
                      │
                      ↓
                  WasmMethod
                      │
                      ↓
                  Browser
                      │
             ┌────────┴────────┐
             │                 │
             ↓                 ↓
      wasm-bindgen        Raw WebAssembly
             │                 │
             └────────┬────────┘
                      ↓
                    Rust
                      │
                      ↓
                  WebForms
                      │
                      ↓
          WebForms Core Response
                      │
                      ↓
                 WebFormsJS
                      │
                      ↓
                  HTML DOM
Enter fullscreen mode Exit fullscreen mode

This makes Rust WebAssembly more than a way to run Rust code in the browser.

It becomes another execution environment capable of participating in the WebForms Core server-orchestrated command architecture.

The same model can therefore combine:

  • Rust
  • WebAssembly
  • Raw WebAssembly
  • wasm-bindgen
  • WebForms Core
  • WebForms Core commands
  • WebFormsJS
  • Standard HTML

without requiring the application to become a traditional JavaScript SPA.


Conclusion

Rust WebAssembly and WebForms Core provide an interesting combination.

With wasm-bindgen, Rust can expose a convenient WebAssembly API with JavaScript interoperability.

With Raw WebAssembly, Rust can expose functions directly through extern "C" using a developer-defined ABI.

Both approaches can participate in the WebForms Core execution model.

The result is a different way of thinking about WebAssembly:

WebAssembly does not have to replace the UI architecture. It can become an execution layer inside the UI architecture.

When that execution layer can directly use WebForms, Rust code can generate WebForms Core commands instead of implementing a separate client-side DOM architecture.

This extends the WebForms Core execution model from:

Server
  ↓
WebForms
  ↓
Commands
  ↓
WebFormsJS
  ↓
HTML DOM
Enter fullscreen mode Exit fullscreen mode

to also support:

Server
  ↓
WebForms
  ↓
WasmMethod
  ↓
Rust WASM
  ↓
WebForms
  ↓
Commands
  ↓
WebFormsJS
  ↓
HTML DOM
Enter fullscreen mode Exit fullscreen mode

This is one of the capabilities introduced by WebForms Core 2.1.

Related Links

Top comments (0)