Skip to content
This repository was archived by the owner on Aug 1, 2025. It is now read-only.

tuist/swiftler

Repository files navigation

Swiftler

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.

Features

  • πŸš€ 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 and mix 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

Installation

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.

Usage

1. Add Swiftler to Your Elixir Project

Add Swiftler to your dependencies in mix.exs:

def deps do
  [
    {:swiftler, "~> 0.1.0"}
  ]
end

2. Generate Swift NIF Project

Use the Swiftler mix task to set up your Swift NIF:

mix swiftler.new

This will create:

  • native/Package.swift - Swift package configuration
  • native/Sources/YourProject/YourProject.swift - Swift source with example functions
  • lib/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

3. Customize Your Swift Functions

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
}

4. Compile Swift Code

Run the Mix task to compile your Swift code:

mix swift.compile

5. Use Your Swift Functions

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

6. Test Your Integration

YourProject.add(5, 3)
# => 8

YourProject.multiply(4, 7)
# => 28

Mix Tasks

  • mix swift.compile - Compiles the Swift package and generates dynamic libraries
  • mix swift.clean - Cleans Swift build artifacts
  • mix swiftler.new - Generates a new Swift NIF project structure

Architecture

Swiftler follows a macro-driven approach inspired by Rustler:

  1. Compile-time Integration: Swift compilation happens during Elixir module compilation via the __using__ macro
  2. Automatic Source Tracking: Swift source files are registered as @external_resource for automatic recompilation
  3. Swift Macro System: Swift macros (@nif and #nifLibrary) generate C-compatible NIF code at compile time
  4. Dynamic Library Generation: Swift code is compiled into a dynamic library using Swift Package Manager
  5. 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.

Development Status

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.

Testing

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

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes
  4. Run tests (mix test)
  5. Commit your changes (git commit -m 'Add amazing feature')
  6. Push to the branch (git push origin feature/amazing-feature)
  7. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • Inspired by Rustler for Rust-Elixir integration
  • API design influenced by swift-nif prototype
  • Built on top of Swift Package Manager and Elixir's NIF system

About

Use Swift from Elixir using NIFs

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •