Skip to content

Swift interop

Swift interop currently uses interfaces Swift itself knows how to generate. Z does not pretend to have a complete native Swift ABI implementation.

Swift classes visible to Objective-C can be imported directly from the generated Module-Swift.h header:

import console from "std/console";
import Swift from "ZSwiftExample-Swift.h";
function main(): i32 {
const counter = Swift.ZSwiftCounter.alloc().initWithValue(40);
counter.value = 41;
console.log(`swift=${counter.add(1)}`);
return 0;
}

Declare the Swift build in z.json:

{
"target": {
"name": "swift-interop",
"entry": "main.zs",
"swift": {
"module": "ZSwiftExample",
"sources": ["Counter.swift"],
"frameworks": ["Foundation"]
}
}
}

The Z compiler builds the Swift library and compatibility header, shares the generated include directory with compiler and editor analysis, then links the products into the native executable.

Swift-native value types can cross through Swift’s generated C++ interface:

{
"target": {
"name": "swift-values",
"entry": "main.zs",
"swift": {
"module": "ZSwiftOwned",
"sources": ["OwnedCounter.swift"],
"interop": "cxx"
}
}
}
import Swift from "ZSwiftOwned-Swift.hpp";
const first = Swift.OwnedCounter.init(40);
const copied = first;
let changed = Swift.roundTrip(copied);
changed.setValue(42);

The generated value wrapper’s native copy constructor and destructor become Z copy and cleanup behavior. Swift associated-value enums can likewise use their generated case factories when the C++ interface exposes them.

This is direct use of compiler-generated compatibility code—not a handwritten Z shim—but it remains bounded by what the installed Swift toolchain exports.

An Objective-C-compatible Swift method may expose an escaping completion block. An adjacent .zd can turn the relationship into an awaitable overload:

callback completion {
thread any;
invoked once;
}
async completion(value, error) {
return value;
throw error;
}

The resulting Z call produces typed structured work:

const result = attempt await worker.calculateChecked(42, false);
match (result) {
success(value) => publish(value);
failure(error) => report(error);
}

invoked once is a behavioral contract supplied by the library author. Z keeps the task frame alive until completion, checks duplicate invocation, enters the runtime for callbacks arriving on foreign threads, and preserves the raw callback-taking overload for callers that need it.

Main-actor metadata enters the same executor model as Objective-C:

const value = await on thread.main worker.calculate(42);

The current strategy reaches a useful set of Apple APIs and Swift packages whose public surface is representable through generated Objective-C or C++ compatibility interfaces. It does not yet provide general direct Swift ABI interop.

Important remaining boundaries include:

  • noncopyable (~Copyable) Swift values unavailable to generated C++;
  • native Swift protocols and generic metadata;
  • general Swift package/module import;
  • Swift-native async functions not exported through a compatible callback;
  • ownership and resilience rules that require direct Swift ABI metadata.

Those may eventually justify a direct Swift importer and ABI layer. The current compatibility route is intentionally useful without freezing Z around unstable or private implementation details.

Runnable examples: Swift class, Swift owned value, Swift enum, and callback-to-task.

Next: Exporting Z through C.