swiftui-native 0.0.1

SwiftUI from pure Rust — no Swift bridge, no shim, only swift-runtime calls
Documentation

swiftui-native

SwiftUI from pure Rust. No Swift source files, no bridging header, no generated shim — the views are built by calling SwiftUI's own entry points through the Swift runtime.

use swiftui_native::{prelude::*, swiftui};

fn main() {
    run(|| swiftui! {
        VStack(spacing: 8.0) {
            Text("Hello from Rust").text_style(.large_title)
            Text("No Swift source files, no bridging header.")
        }
        .padding(20.0)
    });
}

A SwiftUI view is a type: VStack { Text("hi"); Divider() } is VStack<TupleView<(Text, Divider)>>, and Swift decides that when it compiles the file. There is no Swift compiler in this process, so the types are composed at run time from metadata the runtime hands back — which is also what lets a screen's types be chosen while the program is running, something a SwiftUI app cannot do.

What works

  • 425 of the 427 documented View modifiers, reachable under their Swift-derived names. The two that are not are named, with the reason, by cargo run --example reachable.
  • State — @State, @Binding, @AppStorage, @FocusState, @Namespace, and @Observable models owned by Rust that SwiftUI re-renders on.
  • Protocols implemented from Rust — Layout, Shape, ButtonStyle, LabelStyle, ViewModifier, Transition, CustomAnimation, NSViewRepresentable, EnvironmentKey, ContainerValueKey, FocusedValueKey, each a witness table synthesised and registered at run time.
  • App lifecycle — a runtime-synthesised App conformance, WindowGroup, Settings, Window, MenuBarExtra and commands.
  • Swift concurrency — .task { } and detached Tasks created through swift_task_create.
  • macOS and iOS, device or simulator, arm64.

How it is checked

Against Swift, not against itself. cargo run --example parity generates a Swift reference for every case, builds it, and compares all three of:

  • the pixels — 2912 renders over four backdrops, byte for byte;
  • the Swift type each case builds, as the runtime spells it;
  • a recursive dump of the value, arguments included.

cargo run --example ios_parity does the same in a simulator. Agreement on the type is the strong claim: two different routes can draw the same picture, and only one of them is the route Swift takes.

Requirements

  • arm64 macOS or iOS. Not portable to x86_64 — the calling-convention work is arm64 inline assembly.
  • The Swift 6.3+ runtime, which ships with the OS.
  • Rust 1.82 or newer.

Important

The Swift runtime rpath

Some Swift runtime libraries — libswift_Concurrency.dylib among them — have an install name of @rpath/lib….dylib and live only in the dyld shared cache, with no file on disk. A binary that link-references one needs /usr/lib/swift on its LC_RPATH or it compiles and then dies at launch with Library not loaded: @rpath/libswift_Concurrency.dylib.

This crate does not make you deal with that: it resolves those entry points with dlsym instead of linking them, so nothing names the library in a load command. But a build script cannot add an rpath for a dependent — cargo::rustc-link-arg applies to its own package's targets only — so if your crate links a Swift dylib of that kind directly, add it yourself:

# .cargo/config.toml
[target.aarch64-apple-darwin]
rustflags = ["-C", "link-arg=-Wl,-rpath,/usr/lib/swift"]

Memory

  • @ViewBuilder bodies that SwiftUI stores are leaked deliberately. A sheet's content, an alert's actions, a context menu's items are called long after the binding that made them returned, so the context and a copy of the child outlive it. Building such a view once per process costs nothing; rebuilding one every frame leaks every frame.
  • ViewHandle::as_ptr mints a new AnyView per call — a +1 box for a callee to consume. Take one without handing it to Swift and it is leaked.

Where the types differ from Swift's

Two named exceptions to "builds the same type Swift does":

  • A Rust closure run per layout has no result type when the container is built, so GeometryReader, EnvironmentReader and Canvas erase their content to AnyView.
  • A Tab's content is boxed, and the whole TabContent chain with it — the tab, the TupleTabContent over several, and the TabView around them each name Tab<Never, AnyView, DefaultTabLabel> in their own type.

Licence

GPL-3.0 applies to anything linking this crate — a stronger condition than the MIT/Apache-2.0 most of the ecosystem uses. Check it before depending on this.

Disclaimer

This reads and writes Swift's runtime data structures directly — metadata records, witness tables, type descriptors, key path patterns, conformance records — and calls SwiftUI by mangled symbol name. Most of that is ABI Swift documents as stable; some is layout that is stable in practice rather than by promise.

A Swift or OS update can break it, and not with a compile error — it will be a null metadata pointer, a wrong pixel, or a crash inside a framework. That is why the parity suites compare against a Swift reference built on the same machine rather than against recorded expectations. Run them after any toolchain change.

Availability is measured rather than assumed: symbols are probed with dlsym and the answers checked, because Apple's availability annotations and what a given OS exports are not the same set.

Not affiliated with or endorsed by Apple. Pre-1.0, with no stability promise across versions yet.

In this stack

crate what it is
swiftui-native this crate: SwiftUI itself
swiftui-native-macros the swiftui! macro
swift-runtime a safe layer over the runtime
swift-runtime-sys raw runtime bindings

Licence

GPL-3.0. See the repository.