Skip to main content

swiftui_native/
lib.rs

1//! # SwiftUI Native — pure Rust, zero Swift source files
2//!
3//! Constructs SwiftUI views entirely from Rust using:
4//! - `dlsym` to resolve symbols from `SwiftUI.framework`
5//! - arm64 inline assembly for Swift calling convention
6//! - `swift-runtime-sys` for retain/release and string creation
7//!
8//! ```text
9//! ┌────────────┐    dlsym     ┌──────────────────────┐
10//! │  Rust code │ ──────────►  │ SwiftUI.framework    │
11//! │  (arm64    │   Swift CC   │ (Text, Color, etc.)  │
12//! │   asm)     │ ──────────►  │                      │
13//! └────────────┘              └──────────────────────┘
14//! ```
15//!
16//! # Quick start
17//!
18//! ```no_run
19//! use swiftui_native::{prelude::*, swiftui};
20//!
21//! run(|| swiftui! {
22//!     VStack(spacing: 8.0) {
23//!         Text("Hello from pure Rust!").text_style(.large_title)
24//!         Text("No Swift source files, no bridging header.")
25//!     }
26//!     .padding(20.0)
27//! });
28//! ```
29//!
30//! [`swiftui!`] is SwiftUI's own syntax; the same tree written as calls is
31//! `vstack_spacing(&[…], Some(8.0)).padding(20.0)`, and [`views`] underneath that
32//! is the layer where every argument is explicit. All three are the same thing —
33//! pick the one that suits the code around it.
34//!
35//! # Platform
36//!
37//! arm64 macOS and iOS, device or simulator. Requires the Swift 6.3+ runtime.
38//!
39//! Almost none of this is platform-specific: SwiftUI's mangled symbols and the
40//! Swift calling convention are the same everywhere, so [`views`], [`abi`],
41//! [`state`], [`view`] and the rest are shared verbatim. What differs is the
42//! host UI framework SwiftUI draws through, and it differs in three places:
43//!
44//! * [`hosting`] — AppKit has `NSHostingView`, a view. UIKit has only
45//!   `UIHostingController`, so there the view comes from `.view` and the
46//!   SwiftUI side lives on the controller.
47//! * [`render`] — `cacheDisplayInRect:` has no UIKit counterpart, so iOS
48//!   rasterises the layer into a CoreGraphics bitmap instead. The window,
49//!   sheet, popover and compositor captures are AppKit-only.
50//! * [`window`] — an `NSWindow` takes a content view and has a title bar; a
51//!   `UIWindow` takes a root view controller and has neither.
52//!
53//! # Conforming to SwiftUI's protocols, from Rust
54//!
55//! Several of SwiftUI's protocols exist to be implemented by the *caller*:
56//! `Layout`, `ButtonStyle`, `ViewModifier`, `Shape`, `Transition`,
57//! `CustomAnimation`, `NSViewRepresentable`, and the three keyed bags —
58//! `EnvironmentKey`, `FocusedValueKey`, `ContainerValueKey`. A Swift program writes a `struct` and
59//! the compiler emits a witness table; there is no compiler here, so the witness
60//! table is built at run time and registered with
61//! [`swift_registerProtocolConformances`](conformance) — after which SwiftUI
62//! finds it by the same lookup it uses for its own.
63//!
64//! What lands in Rust is a closure. [`views::shape_fn`] is handed the rect and
65//! draws a path; [`views::ns_view_fn`] makes an `NSView` through the
66//! Objective-C runtime and SwiftUI hosts it; [`views::animation_fn`] is handed a time and answers with a
67//! fraction, which is then applied to a value whose type Rust never learns, by
68//! calling `VectorArithmetic.scale(by:)` through that type's own witness table.
69//!
70//! Two things make this harder than it sounds, and both are written up where
71//! they bite: every such callback uses the *witness-method* convention, which no
72//! `extern "C"` signature can express (see [`views::modifier_fn`]'s module); and `Shape`
73//! requires `View`, whose `Body` is a type that cannot be built until `Shape`
74//! exists (see [`views::shape_fn`]'s module).
75//!
76//! # Observation
77//!
78//! `@Observable` works, and the thing that made it look impossible was wrong.
79//! `Observation.Observable` is a **marker protocol** with zero requirements, and
80//! `ObservationRegistrar` and `withObservationTracking` are fully exported — so
81//! the only real obstacle was that an `@Observable` model is a *class* and this
82//! crate mints structs.
83//!
84//! It does not have to be a *Swift* class. `Observable` requires `AnyObject`, an
85//! `NSObject` subclass is one, and [`objc::ClassBuilder`] mints those. The piece
86//! that was genuinely missing was a conformance record whose type reference is a
87//! class rather than a nominal descriptor — `IndirectObjCClass`, one of the four
88//! kinds the format already has, which [`conformance::Conformer`] now emits.
89//!
90//! What that buys is in [`observable`](mod@observable): a model owned by Rust that SwiftUI
91//! re-renders on, without a `@State` write anywhere. [`state::StatefulView`] is
92//! still there for view-local state; this is for a model that outlives the view.
93//!
94//! # Declaring your own keys and models
95//!
96//! An environment key, a container value key, a focused value key and an
97//! `@Observable` model are all things Swift declares with a keyword and this
98//! crate synthesises at run time. Each is a `static` — one name is one
99//! conformance and one key path — so there is a macro per kind that writes the
100//! `static` for you:
101//!
102//! ```no_run
103//! # use swiftui_native::{environment_keys, observable, prelude::*};
104//! environment_keys! {
105//!     /// How deeply this row is nested.
106//!     depth: i64 = 0,
107//!     /// What to greet the reader with.
108//!     greeting: String = "hello",
109//! }
110//!
111//! observable! {
112//!     /// Everything the app's windows share.
113//!     pub AppModel {
114//!         count: i64 = 0,
115//!         name: String = "Ada",
116//!     }
117//! }
118//!
119//! run(|| {
120//!     let model = AppModel::shared();
121//!     environment_reader(move |env| {
122//!         format!("{}, {} — {} deep", env.value_for(greeting()), model.name.get(), env.value_for(depth()))
123//!     })
124//!     .environment_value(depth(), 3)
125//!     .environment_model(model.as_model())
126//! });
127//! ```
128//!
129//! Each property of a model has a key path of its own, so a body that read
130//! `count` is rebuilt when `count` changes and left alone when `name` does. See
131//! [`environment_keys!`], [`container_keys!`], [`focused_keys!`] and
132//! [`observable!`].
133//!
134//! # Known limitations
135//!
136//! **Closure contexts are leaked, deliberately.** A `@ViewBuilder` body that
137//! SwiftUI stores — a sheet's content, an alert's actions, a context menu's
138//! items — is called long after the binding that created it has returned, so
139//! its context and a copy of its child are leaked rather than freed. Building
140//! such a view once per process costs nothing; rebuilding one every frame
141//! leaks every frame. The non-escaping cases (a stack's content, which the
142//! initialiser calls before it returns) borrow instead and leak nothing.
143//! `examples/parity`'s `rebuild_is_bounded` check measures the non-escaping side:
144//! it builds a stack of eleven children a hundred times, divides the growth in
145//! resident memory by a hundred, and fails above 4096 bytes per rebuild. The
146//! escaping cases are not bounded by anything, which is the point of naming them
147//! here.
148//!
149//! **`ViewHandle::as_ptr` mints a new `AnyView` each call.** It hands back a
150//! `+1` box for a callee to consume. Two calls on the same handle return two
151//! different boxes, and a caller that takes one without handing it to Swift
152//! has leaked it.
153//!
154//! **A `@ViewBuilder` closure's result type is fixed when the view is built.**
155//! `GeometryReader` and `EnvironmentReader` take a Rust closure run per layout,
156//! so there is no type to name when the reader is constructed and its content
157//! is erased to `AnyView`. Swift's equivalent is fixed by the compiler, so the
158//! two build different — equally valid — types there. Everything else builds
159//! the same type Swift does, which `examples/parity` checks case by case.
160//!
161//! **A `Tab`'s content is boxed, and the whole `TabContent` chain with it.**
162//! A `Tab` is a `TabContent` rather than a `View`, so what holds it is a
163//! `TabView<_, Content: TabContent>`, and every link — the tab, the
164//! `TupleTabContent` over several of them, the `TabView` around those — names
165//! `Tab<Never, AnyView, DefaultTabLabel>` in its own type. Typing one link and
166//! not the rest describes a value of one layout to code reading another; moving
167//! them together is a `TabContent` composition of its own. Everything else that
168//! knows its children when it is built keeps their types — `Grid`, `GridRow`,
169//! `Section` in all four forms, both `NavigationSplitView` shapes, `Picker`,
170//! `Gauge`, `ViewThatFits`, `Menu`, `DisclosureGroup`, `List`, `TabView`,
171//! `NavigationStack`, `GroupBox`, `Group`, `ControlGroup`, `Form`, `ForEach`,
172//! `TimelineView`, `Toggle` and the stacks — which
173//! [`views::substitute_args`](views) composes per content rather than naming a
174//! fixed instantiation.
175//!
176//! All of these are verified the same way, against a Swift reference built for
177//! the same platform: `examples/parity` on macOS, `examples/ios_parity` in a simulator.
178//! `examples/ios_app` runs a SwiftUI application on iOS through either entry
179//! point — a runtime-synthesised `App` conformance, or `UIApplicationMain` with
180//! a delegate class built through the Objective-C runtime.
181
182#[macro_use]
183pub mod macros;
184pub mod mangle;
185
186pub mod abi;
187pub mod app;
188pub mod cg;
189pub mod color;
190pub mod conformance;
191pub mod diag;
192pub mod hosting;
193pub mod asyncfn;
194pub mod keypath;
195mod thunk;
196pub mod metal;
197pub mod objc;
198pub mod observable;
199pub mod prelude;
200pub mod render;
201pub mod runtime;
202pub mod scene;
203
204/// Runtime synthesis of a Swift view type with real `@State` storage.
205pub mod state;
206pub mod resolve;
207pub mod view;
208pub mod views;
209pub mod window;
210
211// Keep old modules for backward compat
212pub mod existential;
213#[doc(hidden)]
214pub mod handle;
215
216pub use color::Color;
217pub use hosting::HostingView;
218pub use render::Rendered;
219pub use view::View;
220pub use window::Window;
221pub mod dot;
222pub mod defaults;
223
224/// A `&mut` to a `static mut`, taken through a raw pointer.
225///
226/// Two lints disagree about how to write this. `&mut STATIC` is rustc's
227/// `static_mut_refs`; `&mut *(&raw mut STATIC)` in one expression is clippy's
228/// `deref_addrof`, whose suggested fix is the first form. Going through a
229/// function is neither, and the obligation is unchanged either way.
230///
231/// The slots this is used for — a key path's pattern, an async function pointer
232/// record — are each written once, by one caller, before anything reads them.
233/// That is what makes the exclusive borrow sound, and it is a property of those
234/// call sites rather than of this function.
235///
236/// # Safety
237/// No other reference to the static may be live for the returned lifetime.
238pub(crate) unsafe fn static_mut<T>(p: *mut T) -> &'static mut T {
239    unsafe { &mut *p }
240}
241
242pub use views::ViewHandle;
243
244/// `swiftui! { … }` — SwiftUI's declarative syntax, lowered to this crate's DSL.
245///
246/// The DSL is already chainable, so what the macro adds is the part Rust has no
247/// syntax for: a *body* of children written one after another, with `if`,
248/// `match` and `ForEach` inside it.
249///
250/// A string literal in the macro is a format string, so values go in the text
251/// the way Swift's `\(…)` puts them there — `Text("{count} of {total}")`. Field
252/// access works too, `Text("{item.name}")`, which `format!` refuses on its own.
253///
254/// `$name` passes a binding the way Swift passes a projected value —
255/// `Toggle("Dark", isOn: $dark)` — and a leading dot names a case anywhere an
256/// argument is taken, `VStack(alignment: .leading)`.
257///
258/// Swift's further trailing closures carry a label, and work here too:
259/// `Section { … } header: { … }` and `Button { … } label: { … }`.
260///
261/// A trailing brace is content for a modifier that takes a view
262/// — `.overlay { Text("over") }` — and an action for one that takes a callback,
263/// `.on_tap { count += 1 }`. Swift tells those apart by the parameter's type;
264/// with no type to ask, the name does: `on_*`, `task` and `refreshable` take
265/// actions. Swift's `{ value in … }` names the callback's argument.
266///
267/// ```no_run
268/// # use swiftui_native::{swiftui, prelude::*};
269/// # let show_detail = true;
270/// # let items = vec![String::from("one"), String::from("two")];
271/// # enum Status { Ready(String), Waiting }
272/// # let status = Status::Waiting;
273/// let view = swiftui! {
274///     VStack(spacing: 8.0) {
275///         Text("Title").text_style(.large_title)
276///         if show_detail {
277///             Text("the details")
278///         }
279///         match status {
280///             Status::Ready(name) => Text("ready: {name}"),
281///             _ => Text("waiting"),
282///         }
283///         ForEach(0..3) { i in
284///             Text("row {i}")
285///         }
286///         ForEach(items) { item in
287///             Text(item)
288///         }
289///     }
290/// };
291/// ```
292///
293/// See [`swiftui_native_macros`](../swiftui_native_macros/index.html) for what
294/// it does and does not translate.
295pub use swiftui_native_macros::{ltext, stateful, swiftui};