Rust
Checked against f15b998 on 2026-11-10. The files it covers are unchanged since.
niso is written in Rust, but there’s no Rust client crate for app code yet: a
Rust app reaches the DOM the way a C app does, through the C API’s
niso_* functions, as unsafe extern "C" calls. DECISIONS
plans client bindings for Rust generated from the same WebIDL as the JS glue;
they’re not on main.
The timer app in Rust
Section titled “The timer app in Rust”examples/lang/rust
is the timer app as a crate, niso-timer, built as a cdylib that exports
niso_app_main (Native apps). Its
src/niso.rs
declares the part of niso.h the app uses by hand, and a test
(examples/lang/rust/tests/layout.rs) checks that the structs it mirrors have the core’s
layout.
The app wraps the calls it repeats in a small Dom type that turns statuses
into Results:
impl Dom { fn append(self, parent: niso_handle, tag: &str, text: &str) -> Result<niso_handle> { let (mut el, mut added) = (0, 0); unsafe { ok(niso_document_create_element(self.0, niso_dom_document(self.0), tag.as_ptr(), tag.len(), &mut el))?; if !text.is_empty() { ok(niso_node_set_text_content(self.0, el, text.as_ptr(), text.len()))?; } ok(niso_node_append_child(self.0, parent, el, &mut added))?; } Ok(el) }
fn set_attribute(self, el: niso_handle, name: &str, value: &str) -> Result { ok(unsafe { niso_element_set_attribute(self.0, el, name.as_ptr(), name.len(), value.as_ptr(), value.len()) }) }
fn time_stamp(self, event: niso_handle) -> f64 { let mut t = 0.0; unsafe { niso_event_get_time_stamp(self.0, event, &mut t) }; t }}examples/lang/rust/src/lib.rs, line 68
Listeners get a pointer to the app’s state as their userdata. Each holds a reference count, which the DOM gives back through the drop function when it releases the listener:
fn share(state: &Rc<State>) -> *mut c_void { Rc::into_raw(Rc::clone(state)) as *mut c_void}examples/lang/rust/src/lib.rs, line 133
#[unsafe(no_mangle)]pub extern "C" fn niso_app_main(dom: *mut niso_dom) -> i32 { status(build(Dom(dom)))}examples/lang/rust/src/lib.rs, line 299
The library leaves the niso_* symbols to the host it’s loaded into. ELF
linkers allow that by default; on Apple platforms the crate’s
build.rs
passes -undefined dynamic_lookup.
Build and test
Section titled “Build and test”The headless test,
examples/lang/rust/tests/timer.rs,
is the host: it depends on niso-capi for the niso_* functions, makes a
DOM, calls niso_app_main, taps the buttons, runs timers in virtual time
and reads the render stream.
cargo test -q -p niso-timerexamples/lang/rust/build.sh
runs the same build and test. The crate is a workspace member, so
cargo test --workspace runs it too.
Embedding pages from Rust
Section titled “Embedding pages from Rust”A Rust program can also be a host. niso-quickjs’s Js type makes a DOM with
the QuickJS engine attached and runs scripts and HTML pages against it;
niso-run is a small program on it
(niso-run.rs):
pub fn with_scale(width: u32, height: u32, scale: f32) -> Js { let dom = niso_capi::niso_dom_new(width, height, scale); let js = unsafe { niso_js_new(dom.cast()) }; assert!(!js.is_null(), "the JS glue failed to load"); Js { js, dom, loader: None } }crates/niso-quickjs/src/lib.rs, line 61
Js::eval, load_html, set_loader, run and run_until_idle cover what
niso-run does; js.dom() gives the raw DOM for the C API’s runtime calls
(rendering a frame, taps). These are workspace crates, not published ones:
depend on them by path from a checkout. The desktop host,
hosts/iced, is a
complete Rust host built this way. For the crates and what each promises,
see the crate contracts.