swiftui-native 0.0.1

SwiftUI from pure Rust — no Swift bridge, no shim, only swift-runtime calls
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
//! The Swift runtime's own entry points, made safe to call.
//!
//! [`crate::abi`] is about *calling SwiftUI* — the calling convention, metadata,
//! witness tables. This module is about the runtime underneath it: the parts of
//! `libswiftCore` a Swift program uses without ever naming them, because the
//! compiler emits the calls. There is no compiler here, so they are called by
//! hand.
//!
//! Three groups, and they are here together because each is the same kind of
//! thing — a runtime service a Rust caller has no other way to reach:
//!
//! * **Dynamic casts.** `as?` and `as!`, and the twenty other entry points
//!   behind them. What makes this worth having is that a [`ViewHandle`] carries
//!   its metadata at run time, so "is this really a `Text`?" is a question that
//!   can be *asked* rather than tracked.
//! * **Errors.** A Swift `Error` is a box, `swift_allocError` makes one, and a
//!   Rust type can be the thing inside it once its `Error` conformance is
//!   synthesised. That is what lets Rust hand an error *to* Swift.
//! * **Tasks.** [`detached`] runs a Rust closure as a real Swift `Task`, on
//!   Swift's own global executor, with cancellation visible from both sides.
//!   [`crate::asyncfn`] supplies the async closures SwiftUI's `.task` takes;
//!   this starts one from nothing.
//!
//! Everything here is declared in `swift-runtime-sys`; what this adds is the
//! safety argument, the ownership rules, and tests that the calls do what they
//! are supposed to.

use crate::abi;
use crate::views::ViewHandle;
use core::ffi::c_void;

// ── Dynamic casts ──

/// Flags for [`swift_runtime_sys::DynamicCast::swift_dynamicCast`].
///
/// Only the two that matter to a caller who owns the source value and wants to
/// keep it: the cast copies rather than consumes, and leaves the source alone if
/// it fails.
const CAST_COPY: u32 = 0;

/// Is `value` of type `target`?
///
/// `x is T`, without producing the result. The source is left untouched, so this
/// is safe to ask of a value someone else owns.
///
/// # Safety
/// `value` must address an initialised value of `source`'s type, and both
/// metadata pointers must be valid.
pub unsafe fn is_type(
    value: *const c_void,
    source: *const c_void,
    target: *const c_void,
) -> bool {
    unsafe {
        // The destination has to exist even for a question: the runtime writes
        // the cast result into it on success, and a null destination is not a
        // way to ask without receiving.
        let mut scratch = vec![0u8; abi::type_bytes(target).max(1)];
        let ok = swift_runtime_sys::DynamicCast::swift_dynamicCast(
            scratch.as_mut_ptr() as *mut c_void,
            value as *mut c_void,
            source,
            target,
            CAST_COPY,
        );
        if ok {
            let vwt = &*swift_runtime_sys::SwiftABI::get_value_witness_table(target);
            if vwt.size > 0 {
                (vwt.destroy)(scratch.as_mut_ptr() as *mut c_void, target);
            }
        }
        ok
    }
}

/// Is the view a `T`, where `T` is named by its mangled type?
///
/// The question a `ViewHandle` makes askable: it carries its own metadata, so
/// "did that modifier really build a `ModifiedContent`?" is answerable at run
/// time. `dynamic_type_name` gives the same information as a *string*; this
/// gives it as a decision, and without parsing.
///
/// ```no_run
/// # use swiftui_native::{runtime, views, mangle};
/// let t = views::text("hi");
/// assert!(runtime::view_is(&t, &mangle::nominal(&["SwiftUI", "Text"], mangle::Kind::Struct)));
/// ```
pub fn view_is(view: &ViewHandle, mangled: &str) -> bool {
    unsafe {
        let target = abi::resolve_type_by_mangled_name(mangled.as_bytes());
        if target.is_null() {
            return false;
        }
        is_type(view.value_ptr(), views_meta(view), target)
    }
}

/// A handle's metadata, without widening `ViewHandle`'s own surface.
fn views_meta(view: &ViewHandle) -> *const c_void {
    crate::views::meta_of(view)
}

/// Cast a class reference to another class, or `None`.
///
/// `object as? SomeClass` for the class-to-class case, which has its own entry
/// point because it needs no value buffer — a class reference is one word, and
/// the answer is the same word or nothing.
///
/// # Safety
/// `object` must be a live Swift or Objective-C class instance and `target`
/// class metadata.
pub unsafe fn cast_class(object: *const c_void, target: *const c_void) -> Option<*const c_void> {
    let out =
        unsafe { swift_runtime_sys::DynamicCast::swift_dynamicCastClass(object, target) };
    (!out.is_null()).then_some(out)
}

/// Cast a *metatype* to another metatype, or `None`.
///
/// `T.self as? U.Type`. Distinct from [`cast_class`] because a metatype is not
/// an instance: the runtime compares the types themselves.
///
/// # Safety
/// Both arguments must be type metadata.
pub unsafe fn cast_metatype(
    source: *const c_void,
    target: *const c_void,
) -> Option<*const c_void> {
    let out = unsafe {
        swift_runtime_sys::DynamicCast::swift_dynamicCastMetatype(source, target)
    };
    (!out.is_null()).then_some(out)
}

// ── Errors ──

/// A Swift `Error` box, owned.
///
/// What `throw` produces and `catch` receives: a refcounted box holding a value
/// and the `Error` conformance that describes it. Rust can make one, which is
/// how a Rust callback answers a Swift API that wants a failure.
pub struct SwiftError {
    boxed: *mut c_void,
    payload_meta: *const c_void,
}

impl SwiftError {
    /// Box a value as an `Error`.
    ///
    /// # Safety
    /// `value` must address an initialised value of `meta`'s type, and `wt` must
    /// be that type's `Error` conformance. The value is *copied* into the box, so
    /// the caller keeps its own.
    pub unsafe fn new(
        value: *const c_void,
        meta: *const c_void,
        wt: *const c_void,
    ) -> Self {
        unsafe {
            let pair = swift_runtime_sys::ErrorHandling::swift_allocError(
                meta,
                wt,
                value as *mut c_void,
                // `isTake: false` — the box gets a copy and the caller keeps the
                // original. Passing `true` here hands ownership over, and then
                // dropping the caller's copy destroys the box's value.
                false,
            );
            assert!(!pair.object.is_null(), "swift_allocError returned nothing");
            Self { boxed: pair.object, payload_meta: meta }
        }
    }

    /// The box, for handing to Swift.
    ///
    /// Borrowed: Swift is expected to retain it if it keeps it.
    pub fn as_ptr(&self) -> *mut c_void {
        self.boxed
    }

    /// The type of the value inside, as the runtime spells it.
    ///
    /// Read back out of the box rather than remembered, which is what makes this
    /// a check on `swift_allocError` rather than an echo of its argument.
    pub fn payload_type_name(&self) -> String {
        unsafe {
            let mut scratch: *mut c_void = core::ptr::null_mut();
            let mut out = swift_runtime_sys::ErrorHandling::ErrorValueResult {
                value: core::ptr::null(),
                error_type: core::ptr::null(),
                error_conformance: core::ptr::null(),
            };
            swift_runtime_sys::ErrorHandling::swift_getErrorValue(
                self.boxed,
                &mut scratch,
                &mut out,
            );
            assert!(!out.error_type.is_null(), "the error box has no type");
            crate::diag::type_name(out.error_type)
        }
    }

    /// The value inside, as bytes.
    ///
    /// # Safety
    /// The slice borrows the box's storage and is only valid while it lives.
    pub unsafe fn payload(&self) -> &[u8] {
        unsafe {
            let mut scratch: *mut c_void = core::ptr::null_mut();
            let mut out = swift_runtime_sys::ErrorHandling::ErrorValueResult {
                value: core::ptr::null(),
                error_type: core::ptr::null(),
                error_conformance: core::ptr::null(),
            };
            swift_runtime_sys::ErrorHandling::swift_getErrorValue(
                self.boxed,
                &mut scratch,
                &mut out,
            );
            core::slice::from_raw_parts(
                out.value as *const u8,
                abi::type_size(self.payload_meta),
            )
        }
    }
}

impl Clone for SwiftError {
    fn clone(&self) -> Self {
        unsafe {
            swift_runtime_sys::ErrorHandling::swift_errorRetain(self.boxed);
        }
        Self { boxed: self.boxed, payload_meta: self.payload_meta }
    }
}

impl Drop for SwiftError {
    fn drop(&mut self) {
        unsafe { swift_runtime_sys::ErrorHandling::swift_errorRelease(self.boxed) };
    }
}

/// An `Error` holding one `Int`, for the cases that only need to say *which*.
///
/// The `Error` conformance is synthesised the same way every other one in this
/// crate is — `Error` has no requirements of its own beyond the defaults, so the
/// witness table is all defaults and the interesting part is that it is
/// *registered*, which is what makes the box catchable as an `Error` on the
/// Swift side.
pub fn error_code(code: i64) -> SwiftError {
    unsafe {
        let (meta, wt) = *error_carrier();
        let mut value = code;
        SwiftError::new(
            &mut value as *mut i64 as *const c_void,
            meta as *const c_void,
            wt as *const c_void,
        )
    }
}

/// The carrier type and its `Error` conformance, minted once.
fn error_carrier() -> &'static (usize, usize) {
    static C: std::sync::OnceLock<(usize, usize)> = std::sync::OnceLock::new();
    C.get_or_init(|| unsafe {
        let (descriptor, meta) =
            crate::state::emit_int_carrier(b"RustError\0", b"6rswift9RustErrorV\0");
        let protocol = crate::conformance::Protocol::from_symbol(c"$ss5ErrorMp");
        // `Error` is all defaults: `_domain`, `_code`, `_userInfo` and
        // `_getEmbeddedNSError` each have one. A conformance is still needed —
        // the box records it, and `swift_getErrorValue` hands it back.
        let witnesses: Vec<crate::conformance::Witness> = (0..protocol.requirement_count())
            .map(|_| crate::conformance::Witness::Default)
            .collect();
        let conformance = Box::leak(Box::new(crate::conformance::synthesize(
            &protocol,
            descriptor,
            &witnesses,
        )));
        (meta as usize, conformance.witness_table() as usize)
    })
}

// ── Tasks ──

/// A Swift `Task` this crate started, and owns.
///
/// `swift_task_create` hands the task back at `+1` — Swift's own `Task` value
/// holds that reference and releases it when the value goes out of scope, and so
/// does this. Dropping the handle while the task is still running is fine: the
/// runtime keeps its own reference until the task completes.
pub struct Task {
    task: *mut c_void,
    state: std::sync::Arc<crate::asyncfn::TaskState>,
}

// The handle is a refcounted pointer and a shared signal, both of which the
// Swift runtime and `Arc` make safe to move and share between threads. Starting
// a task on one thread and waiting for it on another is the whole point.
unsafe impl Send for Task {}
unsafe impl Sync for Task {}

impl Task {
    /// Has the task been cancelled?
    pub fn is_cancelled(&self) -> bool {
        unsafe { task_is_cancelled(self.task) }
    }

    /// Ask the task to stop.
    ///
    /// Cooperative, as in Swift: it sets a flag the body is expected to check
    /// with [`current_task`]. Nothing is interrupted.
    pub fn cancel(&self) {
        unsafe { task_cancel(self.task) };
    }

    /// The task's base priority, as Swift's `TaskPriority` numbers it.
    pub fn priority(&self) -> u32 {
        unsafe { task_base_priority(self.task) }
    }

    /// Has the closure returned?
    ///
    /// Does not block. True once the body has run, whether it returned or
    /// panicked.
    pub fn is_finished(&self) -> bool {
        self.state.is_finished()
    }

    /// Block until the closure has returned.
    ///
    /// A Swift `Task`'s result is delivered by *awaiting* it, and Rust has
    /// nothing to await from — so the entry signals a condition variable on the
    /// way out and this waits on it. That makes a detached task usable from
    /// ordinary synchronous Rust, which is what it is for here.
    ///
    /// Waiting on the executor's own thread would deadlock, so do not call this
    /// from inside another task.
    pub fn wait(&self) {
        self.state.wait(None);
    }

    /// [`wait`](Self::wait) with a limit. `true` if the task finished in time.
    pub fn wait_timeout(&self, limit: std::time::Duration) -> bool {
        self.state.wait(Some(limit))
    }
}

impl Drop for Task {
    fn drop(&mut self) {
        // The `+1` `swift_task_create` returned. Swift's `Task` releases here
        // too; the runtime holds its own reference until the task completes, so
        // this does not cut a running task short.
        unsafe { swift_runtime_sys::RuntimeRaw::swift_release(self.task) };
    }
}

/// The task running right now — *borrowed*, not owned.
///
/// A separate type from [`Task`] because the two differ in what they may do: a
/// reference obtained from `swift_task_getCurrent` is not one this crate owns,
/// so releasing it would be an over-release, and there is no completion signal
/// for a task somebody else started.
pub struct CurrentTask {
    task: *mut c_void,
}

impl CurrentTask {
    /// Has this task been cancelled?
    ///
    /// The question a long-running body should be asking.
    pub fn is_cancelled(&self) -> bool {
        unsafe { task_is_cancelled(self.task) }
    }

    /// This task's base priority.
    pub fn priority(&self) -> u32 {
        unsafe { task_base_priority(self.task) }
    }
}

/// Run `body` as a detached Swift `Task`, at the default priority.
///
/// See [`detached_with_priority`], which this calls.
pub fn detached(body: impl FnOnce() + Send + 'static) -> Task {
    spawn(None, Box::new(body))
}

/// Run `body` as a detached Swift `Task` at a chosen priority.
///
/// The closure runs on Swift's global concurrent executor, so it must be `Send`
/// and must not touch anything that wants the main thread — which is most of
/// SwiftUI. It is `FnOnce`, because a task runs its body exactly once and owns
/// it: values can be moved in, and the closure is dropped as soon as it returns.
///
/// A panic inside it is caught and reported rather than unwound into the Swift
/// async frame, where there is no landing pad; the task still completes.
///
/// As many at once as you like. `swift_task_create`'s fifth argument is the
/// closure context and the entry receives it in x20, so each task carries its
/// own closure — unlike the `.task` modifier, whose async closure is handed back
/// the context *SwiftUI* was given and so has to live in one of three fixed
/// slots. [`crate::asyncfn`] describes that ceiling; this is the same machinery
/// without it.
///
/// ```no_run
/// # use swiftui_native::runtime;
/// let work = runtime::detached(move || {
///     // …on a cooperative thread…
/// });
/// work.wait();
/// ```
///
/// # Two things this got wrong, both worth knowing
///
/// `swift_task_create` faults *inside itself* when either is wrong, which is a
/// long way from the mistake. Both were found by breaking on the real call in a
/// Swift binary and reading the registers.
///
/// * **The flags are not zero.** `Task.detached` passes `1 << 12` —
///   `Task_EnqueueJob` — which makes `swift_task_create` schedule the task
///   itself. Passing zero and calling `swift_task_enqueue` afterwards is not the
///   same thing.
/// * **`$sytN` is not `()`'s metadata.** It is the *full* metadata record, whose
///   first word is the value witness table; the metadata address point is eight
///   bytes further on. The runtime reads the result type's value witnesses to
///   size the future, so an address eight bytes low reads a function pointer as
///   a size. [`crate::abi::resolve_type_by_mangled_name`] returns the address
///   point, which is what Swift passes.
///
///   The empty tuple is *special* in this, and it is worth saying so before
///   someone generalises the lesson: `$sSiN`, `$sSSN`, `$sSbN` and `$sSdN` were
///   checked against the same lookup and are exactly the address points, which
///   is why the rest of this crate uses them directly. A tuple has no nominal
///   descriptor, so its metadata symbol names a different thing.
pub fn detached_with_priority(
    priority: crate::views::TaskPriority,
    body: impl FnOnce() + Send + 'static,
) -> Task {
    spawn(Some(priority), Box::new(body))
}

/// `Task_EnqueueJob` — bit 12 of `TaskCreateFlags`. The task is scheduled as
/// part of being created, which is what `Task.detached` asks for.
const ENQUEUE_JOB: usize = 1 << 12;

fn spawn(
    priority: Option<crate::views::TaskPriority>,
    body: Box<dyn FnOnce() + Send>,
) -> Task {
    let state = std::sync::Arc::new(crate::asyncfn::TaskState::new());
    unsafe {
        let (record, context) = crate::asyncfn::task_entry_point(body, &state);
        // `Task<Void, Never>`: the success type is `()`.
        let unit = crate::abi::resolve_type_by_mangled_name(b"yt");
        assert!(!unit.is_null(), "() metadata");

        // The low eight bits of the flags are the priority, and the number is
        // read out of Swift's own static rather than written down: a
        // `TaskPriority` is a `UInt8` wrapper, so the first byte of the value its
        // getter returns *is* the raw priority. Zero means "unspecified", which
        // is what `Task.detached` passes when no priority is given.
        let raw = priority.map_or(0usize, |p| {
            let meta = crate::abi::metadata0(crate::resolve::task_priority_ma());
            assert!(!meta.is_null(), "TaskPriority metadata");
            let bytes = crate::views::resilient_static(meta, p.getter());
            usize::from(bytes[0])
        });

        let created = task_create()(
            ENQUEUE_JOB | raw,
            // No options: no task name, no initial executor, no task group.
            core::ptr::null_mut(),
            unit,
            record,
            context,
        );
        assert!(!created.task.is_null(), "swift_task_create returned nothing");
        Task { task: created.task, state }
    }
}

// ── The concurrency entry points, resolved rather than linked ──
//
// `swift-runtime-sys` declares these as ordinary `extern "C"`, which is a `-sys`
// crate's job. Calling them that way, though, puts
// `@rpath/libswift_Concurrency.dylib` in the load commands of whatever links this
// crate — and that library exists only in the dyld shared cache, so the *final
// binary* needs `/usr/lib/swift` on its `LC_RPATH` to find it. A build script's
// `rustc-link-arg` reaches its own package's targets and not a dependent's, so
// this crate cannot add that rpath on a consumer's behalf: an external crate
// linked cleanly and then died in dyld with "Library not loaded".
//
// So they are looked up at run time, as every SwiftUI symbol in this crate is.
// The library is in the process either way — SwiftUI links it — and nothing names
// it in a load command the loader then has to resolve against a path.

/// `swift_task_create(flags, options, resultType, function, closureContext)`.
type TaskCreate = unsafe extern "C" fn(
    usize,
    *mut c_void,
    *const c_void,
    *const c_void,
    *mut c_void,
) -> swift_runtime_sys::ConcurrencyRuntime::AsyncTaskAndContext;

unsafe fn task_create() -> TaskCreate {
    unsafe { core::mem::transmute(crate::resolve::require(c"swift_task_create")) }
}

unsafe fn task_of<R>(name: &'static core::ffi::CStr) -> unsafe extern "C" fn(*mut c_void) -> R {
    unsafe { core::mem::transmute(crate::resolve::require(name)) }
}

/// `swift_task_getCurrent()` — null outside a task.
unsafe fn task_get_current() -> *mut c_void {
    let f: unsafe extern "C" fn() -> *mut c_void =
        unsafe { core::mem::transmute(crate::resolve::require(c"swift_task_getCurrent")) };
    unsafe { f() }
}

/// `swift_task_isCancelled(task)`.
unsafe fn task_is_cancelled(task: *mut c_void) -> bool {
    unsafe { task_of::<bool>(c"swift_task_isCancelled")(task) }
}

/// `swift_task_cancel(task)`.
unsafe fn task_cancel(task: *mut c_void) {
    unsafe { task_of::<()>(c"swift_task_cancel")(task) }
}

/// `swift_task_basePriority(task)`.
unsafe fn task_base_priority(task: *mut c_void) -> u32 {
    unsafe { task_of::<u32>(c"swift_task_basePriority")(task) }
}

/// The task running right now, if any.
///
/// Null outside a task, which is what makes it useful: a Rust closure handed to
/// `.task { }` or to [`detached`] can tell whether Swift's scheduler is what
/// called it, and can ask that task whether it has been cancelled — which is the
/// whole of structured cancellation from this side.
///
/// ```no_run
/// # use swiftui_native::{runtime, views};
/// let v = views::task(
///     &views::text("loading"),
///     0,
///     views::TaskPriority::Utility,
///     file!(),
///     line!() as i64,
///     || {
///         while !runtime::current_task().is_some_and(|t| t.is_cancelled()) {
///             // …work…
///             break;
///         }
///     },
/// );
/// ```
pub fn current_task() -> Option<CurrentTask> {
    let task = unsafe { task_get_current() };
    (!task.is_null()).then_some(CurrentTask { task })
}