Description
At 1Password I've been trying to passively plan out a solution for resolving a lot of clunky code and tech debt in our "foundation" layer. Without making this sound like a product shill/pitch, I'll briefly explain the architecture and constraints. We have three layers of code in the iOS (macOS shares the lower two) app:
- Frontend, which is written entirely in Swift
- "Core" which is written entirely in Rust
- "Foundation" which is a mixture of Rust and Swift code, compiled and linked in by a custom solution I designed and a larger
xcodebuild
produced.a
file.
The FFI layer between the Core and foundation layer isn't great, primarily because the C ABI exposed by Swift is pretty limiting and cumbersome. For example passing state between them opaquely is fragile, returning errors with context out of deeply nested functions doesn't work well, etc.
To resolve this I'm hoping to do two things:
- Use bridging classes (like mentioned in Use extern_class macro with classes declared in swift via @objc #642) for fairly complicated logic or Swift-only functionality where the files are still needed.
- Convert a large amount of the existing Swift code to use the
objc2_*
family of framework crates and "inline" the FFI directly into the calling Rust code.
That last point is what I'm wanting to inquire on. A blocking concern I've found to adopting the objc2
crate family is deprecation and the liveness usually provided by compiling with swiftc
from XCode and the most recent SDK in our build systems. For example, if we are using a function in Swift today that Apple decides to get rid of, we get an XCode-provided deprecation warning since its compiling against the latest SDK. If the same was used via objc2
, we might not know until an arbitrary crate update in the future.
My workaround for this idea was developing a custom linting tool that scans the known callsites of Apple frameworks in Rust, collects the referenced symbols (functions, statics, and constants), and then parses their latest metadata from the current system's SDK. If anything deprecated (and not explicitly allowed) or missing is found, it raises an error to the developer or CI pipeline. For a while I wasn't sure how to do this but I found the header-translator
project while looking at the other objc2
crates. I saw that it collects the deprecation information specifically and translates that nicely to Rust's built-in attributes but beyond that its semi-opaque to me.
The Rust collection side would need to be syn
based, but do you think building a "diffing" tool like this would be feasible with the functionality offered by header-translator
today? Could there potentially be a better way that doesn't damage performance which the framework crates themselves handled at build-time?