Skip to main content

stateful

Macro stateful 

Source
stateful!() { /* proc-macro */ }
Expand description

swiftui! { … } — SwiftUI’s declarative syntax, lowered to this crate’s DSL.

The DSL is already chainable, so what the macro adds is the part Rust has no syntax for: a body of children written one after another, with if, match and ForEach inside it.

A string literal in the macro is a format string, so values go in the text the way Swift’s \(…) puts them there — Text("{count} of {total}"). Field access works too, Text("{item.name}"), which format! refuses on its own.

$name passes a binding the way Swift passes a projected value — Toggle("Dark", isOn: $dark) — and a leading dot names a case anywhere an argument is taken, VStack(alignment: .leading).

Swift’s further trailing closures carry a label, and work here too: Section { … } header: { … } and Button { … } label: { … }.

A trailing brace is content for a modifier that takes a view — .overlay { Text("over") } — and an action for one that takes a callback, .on_tap { count += 1 }. Swift tells those apart by the parameter’s type; with no type to ask, the name does: on_*, task and refreshable take actions. Swift’s { value in … } names the callback’s argument.

let view = swiftui! {
    VStack(spacing: 8.0) {
        Text("Title").text_style(.large_title)
        if show_detail {
            Text("the details")
        }
        match status {
            Status::Ready(name) => Text("ready: {name}"),
            _ => Text("waiting"),
        }
        ForEach(0..3) { i in
            Text("row {i}")
        }
        ForEach(items) { item in
            Text(item)
        }
    }
};

See swiftui_native_macros for what it does and does not translate. Declare a view with property wrappers, by name.

ComposedBuilder works by handing out a handle per property, which is type-safe but leaves the caller threading handles into the body. This declares the same thing and reads every property before the body runs, so the body gets a plain struct with plain fields:

Properties are declared either the way Swift declares them, or the way a Rust reader expects. Both are accepted, including in the same block, so a port can keep the shape of its source and new code can read as Rust:

ⓘ
stateful! {
    pub struct Counter {
        @State var count: i64 = 0
        @State var title: String = String::new()
        @AppStorage("rswift.dark") var dark: bool = false
        @Environment(ColorSchemeKey) var scheme
        @FocusState var editor
    }
}

stateful! {
    pub struct Counter {
        count: state(i64) = 0,
        title: state(String) = String::new(),
        dark: storage(bool) = ("rswift.dark", false),
        scheme: env(ColorSchemeKey),
        editor: focus(),
    }
}

let c = Counter::new(|s| swiftui! { Text("{s.count} {s.title}") });
c.set_count(1);

The difference is only where the key goes: Swift’s wrapper carries it — @AppStorage("k") var dark: bool = false — and Rust’s pairs it with the default, dark: storage(bool) = ("k", false).

A focus() property also gets a <name>_focus field holding the three words .focused(_:) wants, so the body can attach it to a field.