Guides
Objects and identity
A method can return an object and take one. The platform gets a class, the same wrapper every time it asks for the same object.
Returns and parameters#
A method returns an object the way Rust already shares one, behind an Arc, and takes one by reference. Undra generates a class for it in each language, so you never see a handle.
| In Rust | On the platform |
|---|---|
Arc<Shelf>, Option<Arc<Shelf>>, Vec<Arc<Shelf>> returned, also inside a Result or an async method | a Shelf, an optional one, a list of them |
&Shelf, Arc<Shelf>, Option<&Shelf>, Vec<Arc<Shelf>> as a parameter | a Shelf you pass, an optional one, a list |
a constructor returning Arc<Self> | a constructor that gives the same object back while you hold it |
A returned store is a store: observe it and its signals arrive like those of any other.
Show the code for Rust, Swift, Kotlin and TypeScript
#[undra::api]
impl Workshop {
/// The shelf called `name`, made on first use.
pub fn shelf(&self, name: String) -> Arc<Shelf> { .. }
pub fn find(&self, name: String) -> Option<Arc<Shelf>> { .. }
pub fn shelves(&self) -> Vec<Arc<Shelf>> { .. }
pub async fn open(&self, name: String) -> Result<Arc<Shelf>, WorkshopError> { .. }
/// Takes stores as parameters.
pub fn merge(&self, from: &Shelf, onto: &Shelf) { .. }
pub fn total(&self, shelves: Vec<Arc<Shelf>>) -> u32 { .. }
}let inbox = try workshop.shelf(name: "inbox") // a Shelf: observe it like any store
let again = try workshop.shelf(name: "inbox")
assert(inbox === again) // one object, one wrapper
let archive = try workshop.shelf(name: "archive")
workshop.merge(from: inbox, onto: archive)
let all: [Shelf] = try workshop.shelves()
let n = try workshop.total(shelves: all)val inbox = workshop.shelf("inbox") // a Shelf: observe it like any store
check(inbox === workshop.shelf("inbox")) // one object, one wrapper
workshop.merge(from = inbox, onto = workshop.shelf("archive"))
val n = workshop.total(workshop.shelves())const inbox = await workshop.shelf("inbox"); // a Shelf: observe it like any store
console.assert(inbox === (await workshop.shelf("inbox"))); // one object, one wrapper
await workshop.merge(inbox, await workshop.shelf("archive"));
const n = await workshop.total(await workshop.shelves());One object, one wrapper#
Ask for the same object twice and you get the same wrapper while the first is alive. A store returned twice is observed once, a list of shelves is a list you can compare, and in Swift the wrapper is Hashable by identity, so ForEach(shelves, id: \.self) works.
The reference you hold is given back when the wrapper is closed or collected, once. Hold the wrapper for as long as you use it; close() it to let go early.
What stays out#
An object travels as a parameter or a return, and nowhere else.
| Not allowed | Why |
|---|---|
| An object inside a record, a variant or a store's signal, in a stream item, a query or a mutation | a value is copied, compared, saved and merged; an object is none of those. Put the object behind a method (E0064 says so) |
| An object of another core | two cores can issue the same handle numbers, so the generated code refuses it before anything is sent: UndraCallError.refused |
| An object in a port's parameter or return | pass a record or an id instead |
After a restore#
An object a method returned is not part of a saved snapshot: after a restore (a web worker crash, undra dev reloading the core) its handle is stale, and calling the method again returns it. Constructed stores restore as before, and so do query handles: the snapshot keeps what a handle is made of and the core builds it again when the app first uses it (ADR-059). What a store keeps in a private field is whatever its restore hook rebuilds.
Good to know#
- Passing an object lends it. The call holds it until it finishes, so closing the wrapper while a call is in flight is safe.
- A failed call hands nothing out. An error, a cancel or a panic gives the references it had issued back.
- A shelf stays alive while you hold it, even if the core drops its own reference.
The playground's Workshop uses every shape on this page, and the contract scenario S27 checks them on Swift, Kotlin and TypeScript.