Important
Project Archived: After extensive experimentation, we've concluded that integrating Swift with Elixir NIFs presents fundamental challenges that make this approach impractical. The primary blockers include:
- Swift Runtime Initialization: Swift requires its runtime to be properly initialized before any Swift code can execute. Unlike C or Rust, Swift's runtime manages memory (ARC), metadata, and other critical components that must be set up before use.
- ABI Stability Issues: Swift's ABI is only stable on Apple platforms. On Linux and other platforms, the ABI can change between Swift versions, making it difficult to create reliable cross-platform NIFs.
- Memory Management Conflicts: Swift's ARC (Automatic Reference Counting) and Elixir's garbage collector operate on fundamentally different principles, making it challenging to safely pass objects between the two runtimes.
- Symbol Export Complexity: Swift's name mangling and the requirement for
@_cdecl
attributes make it difficult to reliably export C-compatible symbols that Erlang's NIF system expects. - Platform Differences: The different symbol naming conventions between platforms (underscore prefixes on macOS vs. none on Linux) add another layer of complexity.
While it's theoretically possible to make Swift NIFs work with significant workarounds (manual runtime initialization, careful memory management, platform-specific code), the complexity and fragility of such a solution outweigh the benefits. We recommend using Rustler for native code integration with Elixir, as Rust's design aligns much better with the requirements of NIF development.
A utility for calling Swift code from Elixir, similar to how Rustler works for Rust. Swiftler provides seamless integration between Elixir and Swift through dynamic binary generation and automatic NIF binding generation.
- π Dynamic Binary Integration: Swift code is compiled into dynamic binaries and integrated through Elixir's NIF system
- π§ Swift Package Manager: Uses SPM as the build system, following Swift ecosystem conventions
- π¦ Mix Tasks: Provides
mix swift.compile
andmix swift.clean
tasks similar to Rustler - π― Automatic Bindings: Swift macros for automatic NIF binding generation
- π§ͺ Testing Support: Includes Swift Testing framework integration
- πΌ Minimal Dependencies: Lightweight approach with minimal C-related dependencies
- β»οΈ Automatic Recompilation: Tracks Swift source file changes automatically using
@external_resource
- π Rustler-style Integration: Works like Rustler - no need to modify Mix compilers list
Add swiftler
to your list of dependencies in mix.exs
:
def deps do
[
{:swiftler, "~> 0.1.0"}
]
end
Swiftler automatically handles Swift compilation and recompilation - no need to modify your Mix compilers list.
Add Swiftler to your dependencies in mix.exs
:
def deps do
[
{:swiftler, "~> 0.1.0"}
]
end
Use the Swiftler mix task to set up your Swift NIF:
mix swiftler.new
This will create:
native/Package.swift
- Swift package configurationnative/Sources/YourProject/YourProject.swift
- Swift source with example functionslib/your_project.ex
- Elixir module to load the NIF
You can customize the setup with options:
# Custom NIF name and module
mix swiftler.new --name calculator --module MyApp.Calculator
# Custom path
mix swiftler.new --path swift_nif
Edit the generated Swift file to add your custom logic:
import Swiftler
#nifLibrary(name: "calculator", functions: [add(_:_:), multiply(_:_:)])
@nif func add(_ a: Int, _ b: Int) -> Int {
a + b
}
@nif func multiply(_ a: Int, _ b: Int) -> Int {
a * b
}
Run the Mix task to compile your Swift code:
mix swift.compile
The generated Elixir module is ready to use:
# The module was created by mix swiftler.new
defmodule YourProject do
use Swiftler, otp_app: :your_app
# Define Swift function signatures
swift_function add(a: :int, b: :int) :: :int
swift_function multiply(a: :int, b: :int) :: :int
def add(_a, _b), do: :erlang.nif_error(:nif_not_loaded)
def multiply(_a, _b), do: :erlang.nif_error(:nif_not_loaded)
end
YourProject.add(5, 3)
# => 8
YourProject.multiply(4, 7)
# => 28
mix swift.compile
- Compiles the Swift package and generates dynamic librariesmix swift.clean
- Cleans Swift build artifactsmix swiftler.new
- Generates a new Swift NIF project structure
Swiftler follows a macro-driven approach inspired by Rustler:
- Compile-time Integration: Swift compilation happens during Elixir module compilation via the
__using__
macro - Automatic Source Tracking: Swift source files are registered as
@external_resource
for automatic recompilation - Swift Macro System: Swift macros (
@nif
and#nifLibrary
) generate C-compatible NIF code at compile time - Dynamic Library Generation: Swift code is compiled into a dynamic library using Swift Package Manager
- NIF Loading: Elixir loads the dynamic library and calls Swift functions through the NIF interface
This approach eliminates the need to modify your Mix project's compilers list, making it as seamless as Rustler's Rust integration.
Swiftler is currently in development. The macro system generates C-compatible NIF code from Swift functions, and dynamic library generation is working. The project now supports the swift-nif API pattern with @nif
function decorators and #nifLibrary
declarations.
Run the test suite:
mix test
The tests verify that:
- Swift compilation works correctly
- Dynamic binaries are generated
- Elixir macros create proper function stubs
- Type validation works as expected
- NIF integration functions properly
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Make your changes
- Run tests (
mix test
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.