notes/architecture/11-NATIVE-PLUGIN-FFI.md
Build a system that lets Rust projects include Swift/Kotlin/C source files via macros, with metadata emitted through the linker. A bundler extracts this metadata and compiles/links the native code into the final app.
We're adding this feature to manganis by leveraging the recently added cbor support for general assets.
#[manganis::ffi("/src/android")]
extern "Kotlin" {
fn do_thing() -> JObject;
}
#[manganis::ffi("/src/ios")]
extern "Swift" {
fn do_thing() -> NSObject;
}
The macro:
| Problem with build.rs | Our solution |
|---|---|
| Tied to Cargo | Linker metadata works with any build system |
| Slows rust-analyzer | Zero impact on IDE |
| Finicky caching | Bundler handles caching |
| Must run before rustc | Bundler runs after rustc |
The tradeoff: Rust can't use outputs of native compilation (generated headers, etc.). For our use case (runtime FFI via JNI/ObjC), this is fine.
// Annotate an extern block with hardcoded supported languages (swift, kotlin, java, etc)
// Additional props may be passed in as necessary.
#[manganis::ffi("/src/ios", some_prop = "123")]
extern "Swift" {
// objects that may be declared, using `type` syntax
pub type SomeSwiftObject;
// functions associated with said object
// values passed must be pointer-like or values that can be coerced between languages
//
// calling this goes through runtime lookup instead of actually being linked
// for kotlin, this would be a jni lookup and call
pub fn do_thing_with_swift_object_swift(this: &SomeSwiftObject) -> Option<u32>;
}
// User can extend the extern types with their own rusty methods
impl SomeSwiftObject {
pub fn new() -> Self {
// objc code, constructor maybe?
}
// custom extensions
pub fn custom_wrapper(&mut self) {
_ = self.do_thing_with_swift_object_swift().unwrap();
}
}
# iOS
swiftc -target arm64-apple-ios15.0 \
-sdk /path/to/iPhoneOS.sdk \
-emit-library \
-o libplugins.a \
LocationManager.swift
# macOS
swiftc -emit-library \
-o libplugins.dylib \
LocationManager.swift
For Android, you need to produce DEX:
# Compile to class files
kotlinc Geolocator.kt -include-runtime -d classes/
# Convert to DEX
d8 classes/*.class --output dex/
Or use Gradle if dependencies are needed.
# Android
$NDK/toolchains/llvm/prebuilt/*/bin/clang \
--target=aarch64-linux-android21 \
-c fast_math.c -o fast_math.o
# iOS
clang -target arm64-apple-ios15.0 \
-isysroot /path/to/iPhoneOS.sdk \
-c fast_math.c -o fast_math.o
trybuild or manual inspection