Ferris the Crab looking happy

Rust in the Frontend

A brief guide to making websites with Rust in 2023

Daniel Mason

Jackie Chan: Why would you do that?

Why would I want to do that?

Rust is fast

Rust is a language known for its speed

WASM is fast

WASM is known to provide a secure, high performance runtime for the web

Rust WASM ecosystem is very mature

Rust is robust, reliable and 'correct'

Typescript:

async function getUser(email: string): Promise<Result<Error, User>> { 
  const response = await fetch(`https://example.com/${email}`);
  const user = await response.json();
  return user.email === email
    ? Result.ok(user)
    : Result.error(new Error('Incorrect user returned');
}

Rust:

async fn getUser(email: &str) -> Result<User, GetUserError> {
    let user: User = get(format!("https://example.com/{email}")).await?
        .json().await?;
    if user.email == email {
        Ok(user)
    } else {
        Err(GetUserError::IncorrectUserReturned)
    }
}

What are my options

Counter Example

A web page with a counter that can be increased or decreased

Yew

  • Yew looks a lot like React
  • Uses a Virtual DOM
  • Supports CSR, SSR, and SSG

Yew Counter

#[function_component]
fn Counter() -> Html {
    let counter = use_state(|| 0);
    let increment = {
        let counter = counter.clone();
        move |_| { counter.set(*counter + 1); }
    };
    let decrement = {
        let counter = counter.clone();
        move |_| { counter.set(*counter - 1); }
    };

    html! {
        <div>
            <p>{ *counter }</p>
            <button onclick={increment}>{ "+1" }</button>
            <button onclick={decrement}>{ "-1" }</button>
        </div>
    }
}

Yew Counter Usage

#[function_component]
fn App() -> Html {
    html! {
        <>
            <h1>{"Counter Example - Yew"}</h1>
            <Counter />
        </>
    }
}

Perseus (Sycamore)

  • Perseus is to Sycamore what Next is to React
  • Does not use Virtual DOM, uses 'fine grain reactivity'
  • Supports CSR, SSR, and SSG

Perseus Counter

#[component]
#[auto_scope]
fn Counter<G: Html>(cx: Scope, state: &CounterStateRx) -> View<G> {
    let increment = move |_| { state.total.set(*state.total.get() + 1) };
    let decrement = move |_| { state.total.set(*state.total.get() - 1) };

    view! { cx,
        div {
            p { (state.total.get()) }
            button( on:click = increment}) { "+1" }
            button( on:click = decrement) { "-1" }
        }
    }
}

Perseus State

#[derive(Default, Serialize, Deserialize, Clone, ReactiveState)]
#[rx(alias = "CounterStateRx")]
struct State {
    total: i32,
}

Perseus Counter Usage

#[auto_scope]
fn index_page<G: Html>(cx: Scope, state: &CounterStateRx) -> View<G> {
    view! { cx,
        h1 { "Counter Example - Perseus" }
        Counter(state)
    }
}

Dioxus

  • Similar 'reactivity' approach to Sycamore
  • Looks a lot like Sycamore too
  • Supports CSR, SSR, SSG, Desktop, and more!

Dioxus Counter

fn Counter(cx: Scope) -> Element {
    let mut count = use_state(cx, || 0);

    render!(
        p { "{count}" }
        button { onclick: move |_| count += 1, "+1" }
        button { onclick: move |_| count -= 1, "-1" }
    )
}

Dioxus Counter Usage

fn App(cx: Scope) -> Element {
    render!(
        h1 { "Counter Example - Dioxus" }
        Counter { }
    )
}

Comparison

Namedioxus
v0.4.0
sycamore
v0.8.0
react
v18.2.0
yew
v0.20.0
create rows39.7ms45.2ms45.6ms69.4ms
replace all rows43.1ms49.8ms48.4ms73.3ms
partial update83.6ms90.5ms103.2ms100.0ms
select row13.4ms17.7ms24.1ms21.6ms
swap rows26.8ms26.2ms160.7ms ⚠️27.0ms
remove row40.9ms41.3ms43.5ms42.1ms
create many rows433.3ms569.3ms619.2ms2,386.9ms ⚠️
append rows to large table91.8ms100.5ms99.5ms153.8ms
clear rows32.8ms33.0ms30.7ms52.0ms
geometric mean
of all factors in the table
1.161.291.671.90

Results from js-framework-benchmark

Conclusion

Wait, should I even do this

Probably not

  • Speed isn't everything
  • Speed differences are small
  • Converting between HTML and these DSLs is painful
  • We all already know React
  • None of these frameworks are stable

Probably not

Kombucha Woman No

But Maybe

Kombucha Woman Yes

But Maybe

  • These speeds are for rendering
  • Speed differences are much greater for raw calculations
  • Isomorphism means these work great with Rust based servers
  • You get all that correctness goodness I mentioned earlier

Rust in the Frontend in 2023?

Probably not

...but maybe