Skip to content

Tell

A tell is a command. JS asks Rust to do something, and Rust does it immediately.

Unlike a hail, a tell is not a subscription. Nothing is pushed back later.

const pier = usePier();
pier.tell("Increase");
pier.tell({ PushItem: 7 });
#[derive(Rets, Serialize, Deserialize)]
pub enum Tell {
#[ret(i32)]
Increase,
PushItem(i32),
#[ret(Option<i32>)]
PopItem,
}
wasm_bindgen_tell!(Tell, run_tell, Converter);
fn run_tell(tell: Tell) -> JsValue {
let state = use_context::<Stock<State>>().unwrap();
match tell {
Tell::Increase => {
let new_count = {
let mut count = state.count().write();
*count += 1;
*count
};
serde_wasm_bindgen::to_value(&new_count).unwrap()
}
Tell::PushItem(v) => {
state.items().write().push(v);
JsValue::undefined()
}
Tell::PopItem => {
let popped = state.items().write().pop();
serde_wasm_bindgen::to_value(&popped).unwrap()
}
}
}

A tell runs inside its pier, so use_context finds whatever that pier provided.

Add #[ret(..)] and the tell returns that type in TypeScript.

const next = tell("Increase"); // number
const popped = tell("PopItem"); // number | undefined

Leave #[ret(..)] off and the return type is undefined. Return JsValue::undefined() from the runner to match.

Note the extra braces in Tell::Increase:

let new_count = {
let mut count = state.count().write();
*count += 1;
*count
};

write() returns a guard. While it is alive, that value cannot be read again, including by the propagation your write triggers. Keep the guard in a small scope and drop it before you do anything else.

Holding it too long panics with a clear message pointing at your line, rather than deadlocking.

Both change state. They differ in where the logic lives.

Write sends a value straight into a stock. The JS side decides what the new value is. Good for form fields, toggles, sliders.

Tell asks Rust to decide. Good for validation, computation, or anything touching several pieces of state at once.

A rule of thumb: if JS would have to know a business rule to compute the new value, make it a tell.

A tell returns immediately, so it is the wrong place to await something.

For async work, store an Action in context during run_pier and have the tell start it. The action mutates state as it goes, and the hails watching that state update on their own.

Tell::StartTicker(x) => {
let Ticker(ticker) = use_context::<Ticker>().unwrap();
let _ = ticker.call(x);
JsValue::undefined()
}

Both hails and tells declare what they return. Rets is how that reaches TypeScript.