Call Swift functions from Rust with ease!

Overview

swift-rs

Call Swift functions from Rust with ease!

Setup

After adding swift-rs to your project's Cargo.toml, some setup work must be done.

  1. Ensure your swift code is organized into a Swift Package. This can be done in XCode by selecting File -> New -> Project -> Multiplatform -> Swift Package and importing your existing code.
  2. Add SwiftRs as a dependency to your Swift package. A quick internet search can show you how to do this.
  3. Create a build.rs file in your project's source folder, if you don't have one already.
  4. Link the swift runtime to your binary
use swift_rs::build_utils;

fn build() {
    build_utils::link_swift();

    // Other build steps
}
  1. Link your swift package to your binary. link_swift_package takes 2 arguments: The name of your package as specified in its Package.swift, and the location of your package's root folder relative to your rust project's root folder.
use swift_rs::build_utils;

fn build() {
    build_utils::link_swift();
    build_utils::link_swift_package(PACKAGE_NAME, PACKAGE_PATH);

    // Other build steps
}

With those steps completed, you should be ready to start using Swift code from Rust!

Calling basic functions

To allow calling a Swift function from Rust, it must follow some rules:

  1. It must be global and public
  2. It must be annotated with @_cdecl, so that it is callable from C
  3. It must only use types that can be represented in Objective-C, so only classes that derive NSObject, as well as primitives such as Int and Bool. This excludes strings, arrays, generics (though all of these can be sent with workarounds) and structs (which are strictly forbidden).

For this example we will use a function that simply squares a number:

public func squareNumber(number: Int) -> Int {
    return number * number
}

So far, this function meets requirements 1 and 3: It is global and public, and only uses the Int type, which is Objective-C compatible. However, it is not annotated with @_cdecl. To fix this, we must call @_cdecl before the function's declaration and specify the name that the function is exposed to Rust with as its only argument. To keep with Rust's naming conventions, we will export this function in snake case as return_number.

Int { return number * number } ">
@_cdecl("square_number")
public func squareNumber(number: Int) -> Int {
    return number * number
}

Now that returnNumber is properly exposed to Rust, we can start interfacing with it. This is done by pretending that the function is being exposed by a C library, when in reality it is a Swift library exporting a function that looks like it is from C (thanks to @_cdecl):

usize; } ">
extern "C" {
    fn square_number(number: usize) -> usize;
}

Lastly, you can call the function from regular Rust functions. Note that all calls to a Swift function are unsafe, and require wrapping in an unsafe {} block or unsafe fn.

fn main() {
    let input: usize = 4;
    let output = unsafe { square_number(input) };

    println!("Input: {}, Squared: {}", input, output);
    // Prints "Input: 4, Squared: 16"
}

For reference, here is a table for most primitive Rust <-> Swift type conversions

Rust Swift
usize UInt
isize Int
i(N) Int(N)
u(N) UInt(N)
f(N) Float(N)
bool Bool

Returning objects from Swift

Let's say that we want our squareNumber function to return not only the result, but also the original input. A standard way to do this in Swift would be with a struct:

struct SquareNumberResult {
    var input: Int
    var output: Int
}

We are not allowed to do this, though, since structs cannot be represented in Objective-C. Instead, we must use a class that extends NSObject:

class SquareNumberResult: NSObject {
    var input: Int
    var output: Int

    init(_ input: Int, _ output: Int) {
        self.input = input;
        self.output = output
    }
}

Yes, this class could contain the squaring logic too, but that is irrelevant for this example

An instance of this class can then be returned from squareNumber:

SquareNumberResult { let output = input * input return SquareNumberResult(input, output) } ">
@_cdecl("square_number")
public func squareNumber(input: Int) -> SquareNumberResult {
    let output = input * input
    return SquareNumberResult(input, output)
}

As you can see, returning an NSObject from Swift isn't too difficult. The same can't be said for the Rust implementation, though. squareNumber doesn't actually return a struct containing input and output, but instead a pointer to a SquareNumberResult stored somewhere in memory. Additionally, this value contains more data than just input and output: Since it is an NSObject, it contains extra data that must be accounted for when using it in Rust.

This may sound daunting, but it's not actually a problem thanks to SRObject . This type manages the pointer internally, and takes a generic argument for a struct that we can access the data through. Let's see how we'd implement SquareNumbeResult in Rust:

SRObject ; } ">
// Any struct that is used in a C function must be annotated
// with this, and since our Swift function is exposed as a
// C function with @_cdecl, this is necessary here
#[repr(C)]
// Struct matches the class declaration in Swift
struct SquareNumberResult {
    input: usize,
    output: usize
}

extern "C" {
    // SRObject abstracts away the underlying pointer and will automatically deref to
    // &SquareNumberResult through the Deref trait
    fn square_number(input: usize) -> SRObject
    ;
}
   

Then, using the new return value is just like using SquareNumberResult directly:

fn main() {
    let input = 4;
    let result = unsafe { square_number(input) };

    let result_input = result.input; // 4
    let result_input = result.output; // 16
}

Currently, creating objects in Rust and then passing them to Swift is not supported.

Complex types

So far we have only looked at using primitive types and structs/classes, but this leaves out some of the most important data structures: arrays (SRArray ) and strings (SRString). These types must be treated with caution, however, and are not as flexible as their native Swift & Rust counterparts.

Strings

Strings can be passed between Rust and Swift through SRString, which can be created from native strings in either language.

As an argument

import SwiftRs

@_cdecl("swift_print")
public func swiftPrint(value: SRString) {
    // toString() converts the SRString to a Swift String
    print(value.toString())
}
use swift_rs::types::SRString;

extern "C" {
    fn swift_print(value: SRString);
}

fn main() {
    // SRString can be created by simply calling into() on any string reference.
    // This will allocate memory in Swift and copy the string
    let value: SRString = "lorem ipsum".into();

    unsafe { swift_print(value) }; // Will print "lorem ipsum" to the console
}

As a return value

SRString { let value = "lorem ipsum" // SRString can be created from a regular String return SRString(value) } ">
import SwiftRs

@_cdecl("get_string")
public func getString() -> SRString {
    let value = "lorem ipsum"

    // SRString can be created from a regular String
    return SRString(value)
}
SRString; } fn main() { let value_srstring: SRString = unsafe { get_string() }; // SRString can be converted to an &str using as_str()... let value_str: &str = value_srstring.as_str(); // or though the Deref trait let value_str: &str = &*value_srstring; // STString also implements Display println!("{}", value_ststring); // Will print "lorem ipsum" to the console } ">
use swift_rs::types::SRString;

extern "C" {
    fn get_string() -> SRString;
}

fn main() {
    let value_srstring: SRString = unsafe { get_string() };

    // SRString can be converted to an &str using as_str()...
    let value_str: &str = value_srstring.as_str();
    // or though the Deref trait
    let value_str: &str = &*value_srstring;

    // STString also implements Display
    println!("{}", value_ststring); // Will print "lorem ipsum" to the console
}

Arrays

Primitive Arrays

Representing arrays properly is tricky, since we cannot use generics as Swift arguments or return values according to rule 3. Instead, swift-rs provides a generic SRArray that can be embedded inside another class that extends NSObject that is not generic, but is restricted to a single element type.

IntArray { let numbers = [1, 2, 3, 4] return IntArray(numbers) } ">
import SwiftRs

// Argument/Return values can contain generic types, but cannot be generic themselves.
// This includes extending generic types.
class IntArray: NSObject {
    var data: SRArray<Int>

    init(_ data: [Int]) {
        self.data = SRArray(data)
    }
}

@_cdecl("get_numbers")
public func getNumbers() -> IntArray {
    let numbers = [1, 2, 3, 4]

    return IntArray(numbers)
}
SRObject ; } fn main() { let numbers = unsafe { get_numbers() }; // SRArray can be accessed as a slice via as_slice... let numbers_slice: &[usize] = numbers.data.as_slice(); // Or though double deref: Once to get past SRObject, another to get past SRArray let numbers_slice: &[usize] = &**numbers.data; println!("{:?}", numbers_slice); // Will print "[1, 2, 3, 4]" to the console } ">
use swift_rs::types::{SRArray, SRObject};

#[repr(C)]
struct IntArray {
    data: SRArray<usize>
}

extern "C" {
    // Since IntArray extends NSObject in its Swift implementation,
    // it must be wrapped in SRObject on the Rust side
    fn get_numbers() -> SRObject
    ;
}


    fn 
    main() {
    
    let numbers 
    = 
    unsafe { 
    get_numbers() };

    
    // SRArray can be accessed as a slice via as_slice...
    
    let numbers_slice: 
    &[
    usize] 
    = numbers.data.
    as_slice();

    
    // Or though double deref: Once to get past SRObject, another to get past SRArray
    
    let numbers_slice: 
    &[
    usize] 
    = 
    &*
    *numbers.data;

    
    println!(
    "{:?}", numbers_slice); 
    // Will print "[1, 2, 3, 4]" to the console
}
   

To simplify thing on the rust side, however, we can actually do away with the IntArray struct. Since IntArray only has one field, its memory layout is identical to that of SRArray , so our Rust implementation can be simplified at the cost of equivalence with our Swift code:

SRObject >; } ">
extern "C" {
    // We still need to wrap the array in SRObject since
    // the wrapper class in Swift is an NSObject
    fn get_numbers() -> SRObject
     <
     usize>>;
}
    

NSObject Arrays

What if we want to return an NSObject array? There are two options on the Swift side:

  1. Continue using SRArray and a custom wrapper type, or
  2. Use SRObjectArray, a wrapper type provided by swift-rs that accepts any NSObject as its elements. This can be easier than continuing to create wrapper types, but sacrifices some type safety.

There is also SRObjectArray for Rust, which is compatible with any single-element Swift wrapper type (and of course SRObjectArray in Swift), and automatically wraps its elements in SRObject , so there's very little reason to not use it unless you really like custom wrapper types.

Using SRObjectArray in both Swift and Rust with a basic custom class/struct can be done like this:

SRObjectArray { let tuple1 = IntTuple(0,1), tuple2 = IntTuple(2,3), tuple3 = IntTuple(4,5) let tupleArray: [IntTuple] = [ tuple1, tuple2, tuple3 ] // Type safety is only lost when the Swift array is converted to an SRObjectArray return SRObjectArray(tupleArray) } ">
import SwiftRs

class IntTuple: NSObject {
    var item1: Int
    var item2: Int

    init(_ item1: Int, _ item2: Int) {
       self.item1 = item1
       self.item2 = item2
    }
}

@_cdecl("get_tuples")
public func getTuples() -> SRObjectArray {
    let tuple1 = IntTuple(0,1),
        tuple2 = IntTuple(2,3),
        tuple3 = IntTuple(4,5)

    let tupleArray: [IntTuple] = [
        tuple1,
        tuple2,
        tuple3
    ]

    // Type safety is only lost when the Swift array is converted to an SRObjectArray
    return SRObjectArray(tupleArray)
}
since // SRObjectArray does it automatically fn get_tuples() -> SRObjectArray ; } fn main() { let tuples = unsafe { get_tuples() }; for tuple in tuples.as_slice() { // Will print each tuple's contents to the console println!("Item 1: {}, Item 2: {}", tuple.item1, tuple.item2); } } ">
use swift_rs::types::SRObjectArray;

#[repr(C)]
struct IntTuple {
    item1: usize,
    item2: usize
}

extern "C" {
    // No need to wrap IntTuple in SRObject
     
       since
     
    // SRObjectArray
     
       does it automatically
     
    fn get_tuples() -> SRObjectArray
     ;
}


     fn 
     main() {
    
     let tuples 
     = 
     unsafe { 
     get_tuples() };

    
     for tuple 
     in tuples.
     as_slice() {
        
     // Will print each tuple's contents to the console
        
     println!(
     "Item 1: {}, Item 2: {}", tuple.item1, tuple.item2);
    }
}
    

Complex types can contain whatever combination of primitives and SRObject you like, just remember to follow the 3 rules!

Bonuses

SRData

A wrapper type for SRArray designed for storing u8s, essentially just a byte buffer.

Limitations

Currently, the only types that can be used as function arguments are number types, boolean and SRString. This is because those types are easy to allocate memory for, either on the stack or on the heap via calling out to swift, whereas other types are not. This may be implemented in the future, though.

Mutating values across Swift and Rust is not currently an aim for this library, it is purely for providing arguments and returning values. Besides, this would go against Rust's programming model, potentially allowing for multiple shared references to a value instead of interior mutability via something like a Mutex.

Todo

  • Swift class deallocation from rust (implementing Drop and using deallocate_{type} methods)
  • More ease of use and utility functions
Comments
  • Decrement ARC counts on SRObject drop

    Decrement ARC counts on SRObject drop

    After looking over swift-bindgen's code, it seems that it's possible to manually alter ARC counts for arbitrary pointers, meaning that it should be possible to let the swift runtime know when rust is done with an SRObject by decrementing the corresponding ARC. This should avoid memory leaks that I believe exist at the moment, since I don't thing swift knows when rust is finished with a value.

    opened by Brendonovich 10
  • Handle nil

    Handle nil

    How do I handle nil returned from a swift function? The *const pointer in SRObject is private, so I can't check result.0.is_null(). Any other option?

    EDIT: Opened a PR: #5

    opened by 0rvar 5
  • what a some scenarios where such interoperability could be useful?

    what a some scenarios where such interoperability could be useful?

    Hey, just been wandering here and there when i landed on this project. This questions is out of pure curiosity as I am mostly a jr. programmer but rust<->swift interoperability looks like kind of exotic thing. I see that for both rust and swift its important to have access to the legacy of c world, but what are I some potential real world scenarios where swift-rust interop is a thing?

    Just to make things clear - I am genuinely asking and appreciate the work done on this project!

    opened by shengchalover 3
  • Remove Swift Runtime Compatability Version String

    Remove Swift Runtime Compatability Version String

    The version of Swift running on my M1 MacBook Pro does not output a runtime compatibility version string which causes the unwrap to fail in the build script. This small change should fix the issue but I am not sure how it will act on other systems so further testing is certainly required. Also removed native and static declarations as those also caused issues with my system.

    Output of swift -version on my system swift-driver version: 1.26.9 Apple Swift version 5.5.1 (swiftlang-1300.0.31.4 clang-1300.0.29.6) Target: arm64-apple-macosx12.0

    opened by haydenstith 1
  • Does this work in Liux environment too?

    Does this work in Liux environment too?

    Hello. I'm making a Rust program that uses Swift libraries and developing it in a Linux environment (WSL2). Do you know if this binding library works in Linux environments, or not? Thank you in advance.

    opened by yoching 1
  • better handling of minimum macOS version

    better handling of minimum macOS version

    Changes:

    • swift-rs now supports macOS 10.10 (OS X Yosemite) or higher
    • build::link_swift takes an argument for the minimum macOS version. Eg. build::link_swift("10.10");
    • Some docs

    Closes #3

    opened by oscartbeaumont 0
  • Issue with pointer being released

    Issue with pointer being released

    See downstream issue for more details: https://github.com/tauri-apps/tauri/issues/4314

    In theory, I think the memory management mechanism being used on the Swift side is the issue.

    https://github.com/Brendonovich/swift-rs/blob/1c309f652428ecc549fbe8eaccba4835a97d3aa8/src-swift/lib.swift#L66-L69

    It seems like memory is being freed twice.

    opened by lorenzolewis 1
Releases(0.3.0)
  • 0.3.0(Mar 27, 2022)

    New Features

    • Optional NSObject values are now supported (#4)
    • Objects created in Swift are now released when their Rust counterparts are dropped (#2)
    • Build script has been updated to support M1 Macs (#6)

    Changes

    • Build module has been renamed from build_utils to build and is behind corresponding Cargo feature (avoids requiring serde and serde_json to be included by default)
    • Serde support is now behind Cargo feature serde
    • getMounts example no longer leaks huge amounts of memory
    Source code(tar.gz)
    Source code(zip)
Owner
Typescript, Rust and a touch of Elixir
null
The JavaScript runtime that aims for productivity and ease

Byte Byte is a easy and productive runtime for Javascript . It makes making complex programs simple and easy-to-scale with its large and fast Rust API

Byte 32 Jun 16, 2021
Create, open, manage your Python projects with ease, a project aimed to make python development experience a little better

Create, open, manage your Python projects with ease, a project aimed to make python development experience a little better

Dhravya Shah 7 Nov 18, 2022
Safe Rust bridge for creating Erlang NIF functions

Rustler Documentation | Getting Started | Example Rustler is a library for writing Erlang NIFs in safe Rust code. That means there should be no ways t

Rusterlium 3.5k Jan 7, 2023
A rust library containing typings and utility functions dealing with the Public specification of the Internet Computer.

IC Types Contributing Please follow the guidelines in the CONTRIBUTING.md document. Goal This library contains typings and utility functions dealing w

DFINITY 5 Nov 28, 2022
A collection of unsound rust functions using entirly *safe* code

A collection of unsound rust functions using entirly *safe* code

null 2 Sep 6, 2022
Pedersen hashing functions with JS <> Rust interoperability

Pedersen Hash This library exposes the following functions: pub fn pedersen(x: &str, y: &str) -> String: Geometry version. pub fn starknet_pedersen(x:

Herodotus 2 Nov 17, 2022
Unstable wrapper API for winapi's Console Functions

maulingmonkey-console-winapi-wrappers Unstable wrapper API for winapi's Console Functions Quickstart # Cargo.toml [dependencies] maulingmonkey-console

null 3 Nov 26, 2021
Slitter is a C- and Rust-callable slab allocator implemented primarily in Rust, with some C for performance or to avoid unstable Rust features.

Slitter is a less footgunny slab allocator Slitter is a classically structured thread-caching slab allocator that's meant to help write reliable long-

Backtrace Labs 133 Dec 5, 2022
A Rust crate for automatically generating C header files from Rust source file.

Please be aware that this crate is no longer actively maintained, please look into the much more feature rich cbindgen instead. rusty-cheddar rusty-ch

Sean Marshallsay 190 Nov 12, 2022
Rust-ffi-guide - A guide for doing FFI using Rust

Using unsafe for Fun and Profit A guide to traversing the FFI boundary between Rust and other languages. A rendered version is available here. This gu

Michael Bryan 261 Dec 1, 2022
Rust library for build scripts to compile C/C++ code into a Rust library

A library to compile C/C++/assembly into a Rust library/application.

Alex Crichton 1.3k Dec 21, 2022
Rust based WASM/JS bindings for ur-rust

ur-wasm-js WASM/JS bindings for the ur-rust rust library Getting started Installation Either build the library yourself with wasm-pack or install for

Lightning Digital Entertainment 5 Feb 28, 2024
A project for generating C bindings from Rust code

cbindgen   Read the full user docs here! cbindgen creates C/C++11 headers for Rust libraries which expose a public C API. While you could do this by h

Ryan Hunt 1.7k Jan 3, 2023
Automatically generates Rust FFI bindings to C (and some C++) libraries.

bindgen bindgen automatically generates Rust FFI bindings to C (and some C++) libraries. For example, given the C header doggo.h: typedef struct Doggo

The Rust Programming Language 3.2k Jan 4, 2023
Safe interop between Rust and C++

CXX — safe FFI between Rust and C++ This library provides a safe mechanism for calling C++ code from Rust and Rust code from C++, not subject to the m

David Tolnay 4.4k Jan 7, 2023
Bridge the gap between Haskell and Rust

Curryrs Curryrs (a play on the name of Haskell Curry, rs for Rust libraries, and it's pronunciation couriers) is a library for providing easy to use b

Michael Gattozzi 296 Oct 18, 2022
Rust in Haskell FFI Example

Provides an example for using Rust in Haskell. To use this you'll need cargo, rustc, cabal and GHC installed. To execute the example run the following

Michael Gattozzi 21 Oct 1, 2022
Run Java code from Rust!

Java Native Interface Bindings for Rust This library provides complete FFI bindings to the Java Native Interface, as well as a safe and intuitive wrap

Ben Anderson 66 Nov 28, 2022
Embedding Rust in Java

Java/Rust Example An example project showing how to call into Rust code from Java. OSX Linux Windows Requirements Java 7+ Rust (tested with 1.0, night

drrb 318 Jan 1, 2023