├── .gitattributes ├── example ├── swift-lib │ ├── README.md │ ├── .gitignore │ ├── Package.swift │ └── src │ │ └── lib.swift ├── build.rs ├── Cargo.toml ├── src │ └── main.rs └── Cargo.lock ├── .gitignore ├── src-rs ├── types │ ├── mod.rs │ ├── scalars.rs │ ├── data.rs │ ├── object.rs │ ├── string.rs │ └── array.rs ├── lib.rs ├── test-build.rs ├── autorelease.rs ├── swift_ret.rs ├── swift_arg.rs ├── dark_magic.rs ├── swift.rs └── build.rs ├── tests ├── swift-pkg │ ├── lib.swift │ ├── Package.swift │ └── doctests.swift └── test_bindings.rs ├── Cargo.toml ├── Package.swift ├── LICENSE-MIT ├── .github └── workflows │ └── main.yaml ├── src-swift └── lib.swift ├── LICENSE-APACHE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | example/* linguist-vendored 2 | -------------------------------------------------------------------------------- /example/swift-lib/README.md: -------------------------------------------------------------------------------- 1 | # swift 2 | 3 | A description of this package. 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .build/ 2 | target/ 3 | .swiftpm/ 4 | .idea/ 5 | .DS_Store 6 | icon.txt 7 | **/Cargo.lock 8 | -------------------------------------------------------------------------------- /example/swift-lib/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | -------------------------------------------------------------------------------- /src-rs/types/mod.rs: -------------------------------------------------------------------------------- 1 | mod array; 2 | mod data; 3 | mod object; 4 | mod scalars; 5 | mod string; 6 | 7 | pub use array::*; 8 | pub use data::*; 9 | pub use object::*; 10 | pub use scalars::*; 11 | pub use string::*; 12 | -------------------------------------------------------------------------------- /example/build.rs: -------------------------------------------------------------------------------- 1 | use swift_rs::SwiftLinker; 2 | 3 | fn main() { 4 | // Ensure this matches the versions set in your `Package.swift` file. 5 | SwiftLinker::new("10.15") 6 | .with_ios("11") 7 | .with_visionos("1") 8 | .with_package("swift-lib", "./swift-lib/") 9 | .link(); 10 | } 11 | -------------------------------------------------------------------------------- /example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | swift-rs = { path = "../" } 10 | 11 | [build-dependencies] 12 | swift-rs = { path = "../", features = ["build"] } 13 | -------------------------------------------------------------------------------- /src-rs/lib.rs: -------------------------------------------------------------------------------- 1 | //! Call Swift functions from Rust with ease! 2 | #![cfg_attr(docsrs, feature(doc_cfg))] 3 | 4 | mod autorelease; 5 | mod swift; 6 | mod swift_arg; 7 | mod swift_ret; 8 | mod types; 9 | 10 | pub use autorelease::*; 11 | pub use swift::*; 12 | pub use swift_arg::*; 13 | pub use swift_ret::*; 14 | pub use types::*; 15 | 16 | #[cfg(feature = "build")] 17 | #[cfg_attr(docsrs, doc(cfg(feature = "build")))] 18 | mod build; 19 | #[cfg(feature = "build")] 20 | pub use build::*; 21 | -------------------------------------------------------------------------------- /src-rs/test-build.rs: -------------------------------------------------------------------------------- 1 | //! Build script for swift-rs that is a no-op for normal builds, but can be enabled 2 | //! to include test swift library based on env var `TEST_SWIFT_RS=true` with the 3 | //! `build` feature being enabled. 4 | 5 | #[cfg(feature = "build")] 6 | mod build; 7 | 8 | fn main() { 9 | println!("cargo:rerun-if-env-changed=TEST_SWIFT_RS"); 10 | 11 | #[cfg(feature = "build")] 12 | if std::env::var("TEST_SWIFT_RS").unwrap_or_else(|_| "false".into()) == "true" { 13 | use build::SwiftLinker; 14 | 15 | SwiftLinker::new("10.15") 16 | .with_ios("11") 17 | .with_visionos("1") 18 | .with_package("test-swift", "tests/swift-pkg") 19 | .link(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/swift-pkg/lib.swift: -------------------------------------------------------------------------------- 1 | import SwiftRs 2 | import Foundation 3 | 4 | class Complex: NSObject { 5 | var a: SRString 6 | var b: Int 7 | var c: Bool 8 | 9 | public init(a: SRString, b: Int, c: Bool) { 10 | self.a = a 11 | self.b = b 12 | self.c = c 13 | } 14 | } 15 | 16 | @_cdecl("complex_data") 17 | func complexData() -> SRObjectArray { 18 | return SRObjectArray([ 19 | Complex(a: SRString("Brendan"), b: 0, c: true), 20 | Complex(a: SRString("Amod"), b: 1, c: false), 21 | Complex(a: SRString("Lucas"), b: 2, c: true), 22 | Complex(a: SRString("Oscar"), b: 3, c: false), 23 | ]) 24 | } 25 | 26 | @_cdecl("echo_data") 27 | func echoData(data: SRData) -> SRData { 28 | return SRData(data.toArray()) 29 | } 30 | -------------------------------------------------------------------------------- /src-rs/autorelease.rs: -------------------------------------------------------------------------------- 1 | /// Run code with its own autorelease pool. Semantically, this is identical 2 | /// to [`@autoreleasepool`](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html) 3 | /// in Objective-C 4 | /// 5 | /// 6 | /// ```no_run 7 | /// use swift_rs::autoreleasepool; 8 | /// 9 | /// autoreleasepool!({ 10 | /// // do something memory intensive stuff 11 | /// }) 12 | /// ``` 13 | #[macro_export] 14 | macro_rules! autoreleasepool { 15 | ( $expr:expr ) => {{ 16 | extern "C" { 17 | fn objc_autoreleasePoolPush() -> *mut std::ffi::c_void; 18 | fn objc_autoreleasePoolPop(context: *mut std::ffi::c_void); 19 | } 20 | 21 | let pool = unsafe { objc_autoreleasePoolPush() }; 22 | let r = { $expr }; 23 | unsafe { objc_autoreleasePoolPop(pool) }; 24 | r 25 | }}; 26 | } 27 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "swift-rs" 3 | version = "1.0.7" 4 | description = "Call Swift from Rust with ease!" 5 | authors = ["The swift-rs contributors"] 6 | license = "MIT OR Apache-2.0" 7 | repository = "https://github.com/Brendonovich/swift-rs" 8 | edition = "2021" 9 | exclude=["/src-swift", "*.swift"] 10 | build = "src-rs/test-build.rs" 11 | 12 | # /bin/sh RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features 13 | [package.metadata."docs.rs"] 14 | all-features = true 15 | rustdoc-args = ["--cfg", "docsrs"] 16 | 17 | [lib] 18 | path = "src-rs/lib.rs" 19 | 20 | [dependencies] 21 | base64 = "0.21.0" 22 | serde = { version = "1.0", features = ["derive"], optional = true} 23 | serde_json = { version = "1.0", optional = true } 24 | 25 | [build-dependencies] 26 | serde = { version = "1.0", features = ["derive"]} 27 | serde_json = { version = "1.0" } 28 | 29 | [dev-dependencies] 30 | serial_test = "0.10" 31 | 32 | [features] 33 | default = [] 34 | build = ["serde", "serde_json"] 35 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SwiftRs", 8 | platforms: [ 9 | .macOS(.v10_13), 10 | .iOS(.v11), 11 | ], 12 | products: [ 13 | // Products define the executables and libraries a package produces, and make them visible to other packages. 14 | .library( 15 | name: "SwiftRs", 16 | targets: ["SwiftRs"]), 17 | ], 18 | dependencies: [ 19 | // Dependencies declare other packages that this package depends on. 20 | // .package(url: /* package url */, from: "1.0.0"), 21 | ], 22 | targets: [ 23 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 24 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 25 | .target( 26 | name: "SwiftRs", 27 | dependencies: [], 28 | path: "src-swift") 29 | ] 30 | ) 31 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 The swift-rs Developers 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | RUST_BACKTRACE: 1 11 | 12 | jobs: 13 | build: 14 | name: Build 15 | runs-on: macos-latest 16 | strategy: 17 | matrix: 18 | rust: [stable, beta] 19 | steps: 20 | - uses: actions/checkout@v3 21 | name: Checkout 22 | - name: Install specific rust version 23 | run: | 24 | rustup install ${{ matrix.rust }} --profile minimal 25 | rustup component add --toolchain ${{ matrix.rust }} rustfmt clippy 26 | - name: Setup cache 27 | uses: Swatinem/rust-cache@v2 28 | - name: Test example 29 | working-directory: example 30 | run: cargo +${{ matrix.rust }} run 31 | - name: Run Tests 32 | env: 33 | TEST_SWIFT_RS: "true" 34 | run: cargo +${{ matrix.rust }} test --features build 35 | - name: Check Code Formatting 36 | run: cargo +${{ matrix.rust }} fmt --all -- --check 37 | - name: Lints 38 | run: cargo +${{ matrix.rust }} clippy -- -D warnings 39 | -------------------------------------------------------------------------------- /example/src/main.rs: -------------------------------------------------------------------------------- 1 | use swift_rs::{swift, Bool, Int, SRObject, SRObjectArray, SRString}; 2 | 3 | #[repr(C)] 4 | struct Volume { 5 | pub name: SRString, 6 | path: SRString, 7 | total_capacity: Int, 8 | available_capacity: Int, 9 | is_removable: Bool, 10 | is_ejectable: Bool, 11 | is_root_filesystem: Bool, 12 | } 13 | 14 | #[repr(C)] 15 | struct Test { 16 | pub null: bool, 17 | } 18 | 19 | swift!(fn get_file_thumbnail_base64(path: &SRString) -> SRString); 20 | swift!(fn get_mounts() -> SRObjectArray); 21 | swift!(fn return_nullable(null: Bool) -> Option>); 22 | 23 | fn main() { 24 | let path = "/Users"; 25 | let thumbnail = unsafe { get_file_thumbnail_base64(&path.into()) }; 26 | println!( 27 | "length of base64 encoded thumbnail: {}", 28 | thumbnail.as_str().len() 29 | ); 30 | 31 | let mounts = unsafe { get_mounts() }; 32 | println!("First Volume Name: {}", mounts[0].name); 33 | 34 | let opt = unsafe { return_nullable(true) }; 35 | println!("function returned nil: {}", opt.is_none()); 36 | 37 | let opt = unsafe { return_nullable(false) }; 38 | println!("function returned data: {}", opt.is_some()); 39 | } 40 | -------------------------------------------------------------------------------- /tests/swift-pkg/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "test-swift", 8 | platforms: [ 9 | .macOS(.v11), 10 | ], 11 | products: [ 12 | // Products define the executables and libraries a package produces, and make them visible to other packages. 13 | .library( 14 | name: "test-swift", 15 | type: .static, 16 | targets: ["test-swift"]), 17 | ], 18 | dependencies: [ 19 | // Dependencies declare other packages that this package depends on. 20 | .package(name: "SwiftRs", path: "../../") 21 | ], 22 | targets: [ 23 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 24 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 25 | .target( 26 | name: "test-swift", 27 | dependencies: [.product(name: "SwiftRs", package: "SwiftRs")], 28 | path: ".", 29 | exclude: ["test_example.rs", "test_bindings.rs"]) 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /example/swift-lib/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "swift-lib", 8 | platforms: [ 9 | .macOS(.v10_15), // macOS Catalina. Earliest version that is officially supported by Apple. 10 | ], 11 | products: [ 12 | // Products define the executables and libraries a package produces, and make them visible to other packages. 13 | .library( 14 | name: "swift-lib", 15 | type: .static, 16 | targets: ["swift-lib"]), 17 | ], 18 | dependencies: [ 19 | // Dependencies declare other packages that this package depends on. 20 | .package(name: "SwiftRs", path: "../../") 21 | ], 22 | targets: [ 23 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 24 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 25 | .target( 26 | name: "swift-lib", 27 | dependencies: [.product(name: "SwiftRs", package: "SwiftRs")], 28 | path: "src") 29 | ] 30 | ) 31 | -------------------------------------------------------------------------------- /tests/swift-pkg/doctests.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftRs 3 | 4 | // SRArray 5 | // 6 | // Notice that IntArray and ArrayStruct are almost identical! 7 | // The only actual difference between these types is how they're used in Rust, 8 | // but if you added more fields to ArrayStruct then that wouldn't be the case anymore. 9 | 10 | class IntArray: NSObject { 11 | var data: SRArray 12 | 13 | init(data: [Int]) { 14 | self.data = SRArray(data) 15 | } 16 | } 17 | 18 | @_cdecl("get_int_array") 19 | func getIntArray() -> IntArray { 20 | return IntArray(data: [1, 2, 3]) 21 | } 22 | 23 | class ArrayStruct: NSObject { 24 | var array: SRArray 25 | 26 | init(array: [Int]) { 27 | self.array = SRArray(array) 28 | } 29 | } 30 | 31 | @_cdecl("get_array_struct") 32 | func getArrayStruct() -> ArrayStruct { 33 | return ArrayStruct(array: [4, 5, 6]) 34 | } 35 | 36 | // SRObject 37 | 38 | class CustomObject: NSObject { 39 | var a: Int 40 | var b: Bool 41 | 42 | init(a: Int, b: Bool) { 43 | self.a = a 44 | self.b = b 45 | } 46 | } 47 | 48 | @_cdecl("get_custom_object") 49 | func getCustomObject() -> CustomObject { 50 | return CustomObject(a: 3, b: true) 51 | } 52 | 53 | // SRString 54 | 55 | @_cdecl("get_greeting") 56 | func getGreeting(name: SRString) -> SRString { 57 | return SRString("Hello \(name.toString())!") 58 | } 59 | 60 | @_cdecl("echo") 61 | func echo(string: SRString) -> SRString { 62 | return string 63 | } 64 | 65 | // SRData 66 | 67 | @_cdecl("get_data") 68 | func getData() -> SRData { 69 | return SRData([1, 2, 3]) 70 | } 71 | -------------------------------------------------------------------------------- /src-rs/types/scalars.rs: -------------------------------------------------------------------------------- 1 | /// Swift's [`Bool`](https://developer.apple.com/documentation/swift/bool) type 2 | pub type Bool = bool; 3 | 4 | /// Swift's [`Int`](https://developer.apple.com/documentation/swift/int) type 5 | pub type Int = isize; 6 | /// Swift's [`Int8`](https://developer.apple.com/documentation/swift/int8) type 7 | pub type Int8 = i8; 8 | /// Swift's [`Int16`](https://developer.apple.com/documentation/swift/int16) type 9 | pub type Int16 = i16; 10 | /// Swift's [`Int32`](https://developer.apple.com/documentation/swift/int32) type 11 | pub type Int32 = i32; 12 | /// Swift's [`Int64`](https://developer.apple.com/documentation/swift/int64) type 13 | pub type Int64 = i64; 14 | 15 | /// Swift's [`UInt`](https://developer.apple.com/documentation/swift/uint) type 16 | pub type UInt = usize; 17 | /// Swift's [`UInt8`](https://developer.apple.com/documentation/swift/uint8) type 18 | pub type UInt8 = u8; 19 | /// Swift's [`UInt16`](https://developer.apple.com/documentation/swift/uint16) type 20 | pub type UInt16 = u16; 21 | /// Swift's [`UInt32`](https://developer.apple.com/documentation/swift/uint32) type 22 | pub type UInt32 = u32; 23 | /// Swift's [`UInt64`](https://developer.apple.com/documentation/swift/uint64) type 24 | pub type UInt64 = u64; 25 | 26 | /// Swift's [`Float`](https://developer.apple.com/documentation/swift/float) type 27 | pub type Float = f32; 28 | /// Swift's [`Double`](https://developer.apple.com/documentation/swift/double) type 29 | pub type Double = f64; 30 | 31 | /// Swift's [`Float32`](https://developer.apple.com/documentation/swift/float32) type 32 | pub type Float32 = f32; 33 | /// Swift's [`Float64`](https://developer.apple.com/documentation/swift/float64) type 34 | pub type Float64 = f64; 35 | -------------------------------------------------------------------------------- /src-rs/swift_ret.rs: -------------------------------------------------------------------------------- 1 | use crate::{swift::SwiftObject, *}; 2 | use std::ffi::c_void; 3 | 4 | /// Identifies a type as being a valid return type from a Swift function. 5 | /// For types that are objects which need extra retains, 6 | /// the [`retain`](SwiftRet::retain) function will be re-implemented. 7 | pub trait SwiftRet { 8 | /// Adds a retain to the value if possible 9 | /// 10 | /// # Safety 11 | /// Just don't use this. 12 | /// Let [`swift!`] handle it. 13 | unsafe fn retain(&self) {} 14 | } 15 | 16 | macro_rules! primitive_impl { 17 | ($($t:ty),+) => { 18 | $(impl SwiftRet for $t { 19 | })+ 20 | }; 21 | } 22 | 23 | primitive_impl!( 24 | Bool, 25 | Int, 26 | Int8, 27 | Int16, 28 | Int32, 29 | Int64, 30 | UInt, 31 | UInt8, 32 | UInt16, 33 | UInt32, 34 | UInt64, 35 | Float32, 36 | Float64, 37 | *const c_void, 38 | *mut c_void, 39 | (), 40 | *const Bool, 41 | *mut Bool, 42 | *const Int, 43 | *mut Int, 44 | *const Int8, 45 | *mut Int8, 46 | *const Int16, 47 | *mut Int16, 48 | *const Int32, 49 | *mut Int32, 50 | *const Int64, 51 | *mut Int64, 52 | *const UInt, 53 | *mut UInt, 54 | *const UInt8, 55 | *mut UInt8, 56 | *const UInt16, 57 | *mut UInt16, 58 | *const UInt32, 59 | *mut UInt32, 60 | *const UInt64, 61 | *mut UInt64, 62 | *const Float32, 63 | *mut Float32, 64 | *const Float64, 65 | *mut Float64 66 | ); 67 | 68 | impl SwiftRet for Option { 69 | unsafe fn retain(&self) { 70 | if let Some(v) = self { 71 | v.retain() 72 | } 73 | } 74 | } 75 | 76 | impl SwiftRet for T { 77 | unsafe fn retain(&self) { 78 | (*self).retain() 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src-rs/types/data.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | swift::{self, SwiftObject}, 3 | Int, 4 | }; 5 | 6 | use super::{array::SRArray, SRObject}; 7 | 8 | use std::ops::Deref; 9 | 10 | type Data = SRArray; 11 | 12 | /// Convenience type for working with byte buffers, 13 | /// analagous to `SRData` in Swift. 14 | /// 15 | /// ```rust 16 | /// use swift_rs::{swift, SRData}; 17 | /// 18 | /// swift!(fn get_data() -> SRData); 19 | /// 20 | /// let data = unsafe { get_data() }; 21 | /// 22 | /// assert_eq!(data.as_ref(), &[1, 2, 3]) 23 | /// ``` 24 | /// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L68) 25 | #[repr(transparent)] 26 | pub struct SRData(SRObject); 27 | 28 | impl SRData { 29 | pub fn as_slice(&self) -> &[u8] { 30 | self 31 | } 32 | 33 | pub fn to_vec(&self) -> Vec { 34 | self.as_slice().to_vec() 35 | } 36 | } 37 | 38 | impl SwiftObject for SRData { 39 | type Shape = Data; 40 | 41 | fn get_object(&self) -> &SRObject { 42 | &self.0 43 | } 44 | } 45 | 46 | impl Deref for SRData { 47 | type Target = [u8]; 48 | 49 | fn deref(&self) -> &Self::Target { 50 | &self.0 51 | } 52 | } 53 | 54 | impl AsRef<[u8]> for SRData { 55 | fn as_ref(&self) -> &[u8] { 56 | self 57 | } 58 | } 59 | 60 | impl From<&[u8]> for SRData { 61 | fn from(value: &[u8]) -> Self { 62 | unsafe { swift::data_from_bytes(value.as_ptr(), value.len() as Int) } 63 | } 64 | } 65 | 66 | #[cfg(feature = "serde")] 67 | impl serde::Serialize for SRData { 68 | fn serialize(&self, serializer: S) -> Result 69 | where 70 | S: serde::Serializer, 71 | { 72 | serializer.serialize_bytes(self) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src-rs/types/object.rs: -------------------------------------------------------------------------------- 1 | use crate::swift::{self, SwiftObject}; 2 | use std::{ffi::c_void, ops::Deref, ptr::NonNull}; 3 | 4 | #[doc(hidden)] 5 | #[repr(C)] 6 | pub struct SRObjectImpl { 7 | _nsobject_offset: u8, 8 | data: T, 9 | } 10 | 11 | /// Wrapper for arbitrary `NSObject` types. 12 | /// 13 | /// When returning an `NSObject`, its Rust type must be wrapped in `SRObject`. 14 | /// The type must also be annotated with `#[repr(C)]` to ensure its memory layout 15 | /// is identical to its Swift counterpart's. 16 | /// 17 | /// ```rust 18 | /// use swift_rs::{swift, SRObject, Int, Bool}; 19 | /// 20 | /// #[repr(C)] 21 | /// struct CustomObject { 22 | /// a: Int, 23 | /// b: Bool 24 | /// } 25 | /// 26 | /// swift!(fn get_custom_object() -> SRObject); 27 | /// 28 | /// let value = unsafe { get_custom_object() }; 29 | /// 30 | /// let reference: &CustomObject = value.as_ref(); 31 | /// ``` 32 | /// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L49) 33 | #[repr(transparent)] 34 | pub struct SRObject(pub(crate) NonNull>); 35 | 36 | impl SwiftObject for SRObject { 37 | type Shape = T; 38 | 39 | fn get_object(&self) -> &SRObject { 40 | self 41 | } 42 | } 43 | 44 | impl Deref for SRObject { 45 | type Target = T; 46 | 47 | fn deref(&self) -> &T { 48 | unsafe { &self.0.as_ref().data } 49 | } 50 | } 51 | 52 | impl AsRef for SRObject { 53 | fn as_ref(&self) -> &T { 54 | self 55 | } 56 | } 57 | 58 | impl Drop for SRObject { 59 | fn drop(&mut self) { 60 | unsafe { swift::release_object(self.0.as_ref() as *const _ as *const c_void) } 61 | } 62 | } 63 | 64 | #[cfg(feature = "serde")] 65 | impl serde::Serialize for SRObject 66 | where 67 | T: serde::Serialize, 68 | { 69 | fn serialize(&self, serializer: S) -> Result 70 | where 71 | S: serde::Serializer, 72 | { 73 | self.deref().serialize(serializer) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src-rs/types/string.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::{Display, Error, Formatter}, 3 | ops::Deref, 4 | }; 5 | 6 | use crate::{ 7 | swift::{self, SwiftObject}, 8 | Int, SRData, SRObject, 9 | }; 10 | 11 | /// String type that can be shared between Swift and Rust. 12 | /// 13 | /// ```rust 14 | /// use swift_rs::{swift, SRString}; 15 | /// 16 | /// swift!(fn get_greeting(name: &SRString) -> SRString); 17 | /// 18 | /// let greeting = unsafe { get_greeting(&"Brendan".into()) }; 19 | /// 20 | /// assert_eq!(greeting.as_str(), "Hello Brendan!"); 21 | /// ``` 22 | /// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L56) 23 | #[repr(transparent)] 24 | pub struct SRString(SRData); 25 | 26 | impl SRString { 27 | pub fn as_str(&self) -> &str { 28 | unsafe { std::str::from_utf8_unchecked(&self.0) } 29 | } 30 | } 31 | 32 | impl SwiftObject for SRString { 33 | type Shape = ::Shape; 34 | 35 | fn get_object(&self) -> &SRObject { 36 | self.0.get_object() 37 | } 38 | } 39 | 40 | impl Deref for SRString { 41 | type Target = str; 42 | 43 | fn deref(&self) -> &Self::Target { 44 | self.as_str() 45 | } 46 | } 47 | 48 | impl AsRef<[u8]> for SRString { 49 | fn as_ref(&self) -> &[u8] { 50 | self.0.as_ref() 51 | } 52 | } 53 | 54 | impl From<&str> for SRString { 55 | fn from(string: &str) -> Self { 56 | unsafe { swift::string_from_bytes(string.as_ptr(), string.len() as Int) } 57 | } 58 | } 59 | 60 | impl Display for SRString { 61 | fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { 62 | self.as_str().fmt(f) 63 | } 64 | } 65 | 66 | #[cfg(feature = "serde")] 67 | impl serde::Serialize for SRString { 68 | fn serialize(&self, serializer: S) -> Result 69 | where 70 | S: serde::Serializer, 71 | { 72 | serializer.serialize_str(self.as_str()) 73 | } 74 | } 75 | #[cfg(feature = "serde")] 76 | impl<'a> serde::Deserialize<'a> for SRString { 77 | fn deserialize(deserializer: D) -> Result 78 | where 79 | D: serde::Deserializer<'a>, 80 | { 81 | let string = String::deserialize(deserializer)?; 82 | Ok(SRString::from(string.as_str())) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src-swift/lib.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public class SRArray: NSObject { 4 | // Used by Rust 5 | let pointer: UnsafePointer 6 | let length: Int; 7 | 8 | // Actual array, deallocates objects inside automatically 9 | let array: [T]; 10 | 11 | public override init() { 12 | self.array = []; 13 | self.pointer = UnsafePointer(self.array); 14 | self.length = 0; 15 | } 16 | 17 | public init(_ data: [T]) { 18 | self.array = data; 19 | self.pointer = UnsafePointer(self.array) 20 | self.length = data.count 21 | } 22 | 23 | public func toArray() -> [T] { 24 | return Array(self.array) 25 | } 26 | } 27 | 28 | public class SRObjectArray: NSObject { 29 | let data: SRArray 30 | 31 | public init(_ data: [NSObject]) { 32 | self.data = SRArray(data) 33 | } 34 | } 35 | 36 | public class SRData: NSObject { 37 | let data: SRArray 38 | 39 | public override init() { 40 | self.data = SRArray() 41 | } 42 | 43 | public init(_ data: [UInt8]) { 44 | self.data = SRArray(data) 45 | } 46 | 47 | public init (_ srArray: SRArray) { 48 | self.data = srArray 49 | } 50 | 51 | public func toArray() -> [UInt8] { 52 | return self.data.toArray() 53 | } 54 | } 55 | 56 | public class SRString: SRData { 57 | public override init() { 58 | super.init([]) 59 | } 60 | 61 | public init(_ string: String) { 62 | super.init(Array(string.utf8)) 63 | } 64 | 65 | init(_ data: SRData) { 66 | super.init(data.data) 67 | } 68 | 69 | public func toString() -> String { 70 | return String(bytes: self.data.array, encoding: .utf8)! 71 | } 72 | } 73 | 74 | @_cdecl("retain_object") 75 | func retainObject(ptr: UnsafeMutableRawPointer) { 76 | let _ = Unmanaged.fromOpaque(ptr).retain() 77 | } 78 | 79 | @_cdecl("release_object") 80 | func releaseObject(ptr: UnsafeMutableRawPointer) { 81 | let _ = Unmanaged.fromOpaque(ptr).release() 82 | } 83 | 84 | @_cdecl("data_from_bytes") 85 | func dataFromBytes(data: UnsafePointer, size: Int) -> SRData { 86 | let buffer = UnsafeBufferPointer(start: data, count: size) 87 | return SRData(Array(buffer)) 88 | } 89 | 90 | @_cdecl("string_from_bytes") 91 | func stringFromBytes(data: UnsafePointer, size: Int) -> SRString { 92 | let data = dataFromBytes(data: data, size: size); 93 | return SRString(data) 94 | } 95 | -------------------------------------------------------------------------------- /src-rs/swift_arg.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::c_void; 2 | 3 | use crate::{swift::SwiftObject, *}; 4 | 5 | /// Identifies a type as being a valid argument in a Swift function. 6 | pub trait SwiftArg<'a> { 7 | type ArgType; 8 | 9 | /// Creates a swift-compatible version of the argument. 10 | /// For primitives this just returns `self`, 11 | /// but for [`SwiftObject`] types it wraps them in [`SwiftRef`]. 12 | /// 13 | /// This function is called within the [`swift!`] macro. 14 | /// 15 | /// # Safety 16 | /// 17 | /// Creating a [`SwiftRef`] is inherently unsafe, 18 | /// but is reliable if using the [`swift!`] macro, 19 | /// so it is not advised to call this function manually. 20 | unsafe fn as_arg(&'a self) -> Self::ArgType; 21 | } 22 | 23 | macro_rules! primitive_impl { 24 | ($($t:ty),+) => { 25 | $(impl<'a> SwiftArg<'a> for $t { 26 | type ArgType = $t; 27 | 28 | unsafe fn as_arg(&'a self) -> Self::ArgType { 29 | *self 30 | } 31 | })+ 32 | }; 33 | } 34 | 35 | primitive_impl!( 36 | Bool, 37 | Int, 38 | Int8, 39 | Int16, 40 | Int32, 41 | Int64, 42 | UInt, 43 | UInt8, 44 | UInt16, 45 | UInt32, 46 | UInt64, 47 | Float32, 48 | Float64, 49 | *const c_void, 50 | *mut c_void, 51 | *const Bool, 52 | *mut Bool, 53 | *const Int, 54 | *mut Int, 55 | *const Int8, 56 | *mut Int8, 57 | *const Int16, 58 | *mut Int16, 59 | *const Int32, 60 | *mut Int32, 61 | *const Int64, 62 | *mut Int64, 63 | *const UInt, 64 | *mut UInt, 65 | *const UInt8, 66 | *mut UInt8, 67 | *const UInt16, 68 | *mut UInt16, 69 | *const UInt32, 70 | *mut UInt32, 71 | *const UInt64, 72 | *mut UInt64, 73 | *const Float32, 74 | *mut Float32, 75 | *const Float64, 76 | *mut Float64, 77 | () 78 | ); 79 | 80 | macro_rules! ref_impl { 81 | ($($t:ident $(<$($gen:ident),+>)?),+) => { 82 | $(impl<'a $($(, $gen: 'a),+)?> SwiftArg<'a> for $t$(<$($gen),+>)? { 83 | type ArgType = SwiftRef<'a, $t$(<$($gen),+>)?>; 84 | 85 | unsafe fn as_arg(&'a self) -> Self::ArgType { 86 | self.swift_ref() 87 | } 88 | })+ 89 | }; 90 | } 91 | 92 | ref_impl!(SRObject, SRArray, SRData, SRString); 93 | 94 | impl<'a, T: SwiftArg<'a>> SwiftArg<'a> for &T { 95 | type ArgType = T::ArgType; 96 | 97 | unsafe fn as_arg(&'a self) -> Self::ArgType { 98 | (*self).as_arg() 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /example/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "base64" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" 10 | 11 | [[package]] 12 | name = "example" 13 | version = "0.1.0" 14 | dependencies = [ 15 | "swift-rs", 16 | ] 17 | 18 | [[package]] 19 | name = "itoa" 20 | version = "1.0.1" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" 23 | 24 | [[package]] 25 | name = "proc-macro2" 26 | version = "1.0.36" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 29 | dependencies = [ 30 | "unicode-xid", 31 | ] 32 | 33 | [[package]] 34 | name = "quote" 35 | version = "1.0.16" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "b4af2ec4714533fcdf07e886f17025ace8b997b9ce51204ee69b6da831c3da57" 38 | dependencies = [ 39 | "proc-macro2", 40 | ] 41 | 42 | [[package]] 43 | name = "ryu" 44 | version = "1.0.9" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" 47 | 48 | [[package]] 49 | name = "serde" 50 | version = "1.0.136" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" 53 | dependencies = [ 54 | "serde_derive", 55 | ] 56 | 57 | [[package]] 58 | name = "serde_derive" 59 | version = "1.0.136" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" 62 | dependencies = [ 63 | "proc-macro2", 64 | "quote", 65 | "syn", 66 | ] 67 | 68 | [[package]] 69 | name = "serde_json" 70 | version = "1.0.79" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" 73 | dependencies = [ 74 | "itoa", 75 | "ryu", 76 | "serde", 77 | ] 78 | 79 | [[package]] 80 | name = "swift-rs" 81 | version = "1.0.7" 82 | dependencies = [ 83 | "base64", 84 | "serde", 85 | "serde_json", 86 | ] 87 | 88 | [[package]] 89 | name = "syn" 90 | version = "1.0.89" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "ea297be220d52398dcc07ce15a209fce436d361735ac1db700cab3b6cdfb9f54" 93 | dependencies = [ 94 | "proc-macro2", 95 | "quote", 96 | "unicode-xid", 97 | ] 98 | 99 | [[package]] 100 | name = "unicode-xid" 101 | version = "0.2.2" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 104 | -------------------------------------------------------------------------------- /src-rs/dark_magic.rs: -------------------------------------------------------------------------------- 1 | /// This retain-balancing algorithm is cool but likely isn't required. 2 | /// I'm keeping it around in case it's necessary one day. 3 | 4 | // #[derive(Clone, Copy, Debug)] 5 | // enum ValueArity { 6 | // Reference, 7 | // Value, 8 | // } 9 | 10 | // pub unsafe fn balance_ptrs(args: Vec<(*const c_void, bool)>, ret: Vec<(*const c_void, bool)>) { 11 | // fn collect_references( 12 | // v: Vec<(*const c_void, bool)>, 13 | // ) -> BTreeMap<*const c_void, Vec> { 14 | // v.into_iter().fold( 15 | // BTreeMap::<_, Vec>::new(), 16 | // |mut map, (ptr, is_ref)| { 17 | // map.entry(ptr).or_default().push(if is_ref { 18 | // ValueArity::Reference 19 | // } else { 20 | // ValueArity::Value 21 | // }); 22 | // map 23 | // }, 24 | // ) 25 | // } 26 | 27 | // let mut args = collect_references(args); 28 | // let mut ret = collect_references(ret); 29 | 30 | // let both_counts = args 31 | // .clone() 32 | // .into_iter() 33 | // .flat_map(|(arg, values)| { 34 | // ret.remove(&arg).map(|ret| { 35 | // args.remove(&arg); 36 | 37 | // let ret_values = ret 38 | // .iter() 39 | // .filter(|v| matches!(v, ValueArity::Value)) 40 | // .count() as isize; 41 | 42 | // let arg_references = values 43 | // .iter() 44 | // .filter(|v| matches!(v, ValueArity::Reference)) 45 | // .count() as isize; 46 | 47 | // let ref_in_value_out_retains = min(ret_values, arg_references); 48 | 49 | // (arg, ref_in_value_out_retains) 50 | // }) 51 | // }) 52 | // .collect::>(); 53 | 54 | // let arg_counts = args.into_iter().map(|(ptr, values)| { 55 | // let count = values 56 | // .into_iter() 57 | // .filter(|v| matches!(v, ValueArity::Value)) 58 | // .count() as isize; 59 | // (ptr, count) 60 | // }); 61 | 62 | // let ret_counts = ret 63 | // .into_iter() 64 | // .map(|(ptr, values)| { 65 | // let count = values 66 | // .into_iter() 67 | // .filter(|v| matches!(v, ValueArity::Value)) 68 | // .count() as isize; 69 | // (ptr, count) 70 | // }) 71 | // .collect::>(); 72 | 73 | // both_counts 74 | // .into_iter() 75 | // .chain(arg_counts) 76 | // .chain(ret_counts) 77 | // .for_each(|(ptr, count)| match count { 78 | // 0 => {} 79 | // n if n > 0 => { 80 | // for _ in 0..n { 81 | // retain_object(ptr) 82 | // } 83 | // } 84 | // n => { 85 | // for _ in n..0 { 86 | // release_object(ptr) 87 | // } 88 | // } 89 | // }); 90 | // } 91 | -------------------------------------------------------------------------------- /example/swift-lib/src/lib.swift: -------------------------------------------------------------------------------- 1 | import SwiftRs 2 | import AppKit 3 | 4 | @_cdecl("get_file_thumbnail_base64") 5 | func getFileThumbnailBase64(path: SRString) -> SRString { 6 | let path = path.toString(); 7 | 8 | let image = NSWorkspace.shared.icon(forFile: path) 9 | let bitmap = NSBitmapImageRep(data: image.tiffRepresentation!)!.representation(using: .png, properties: [:])! 10 | 11 | return SRString(bitmap.base64EncodedString()) 12 | } 13 | 14 | class Volume: NSObject { 15 | var name: SRString 16 | var path: SRString 17 | var total_capacity: Int 18 | var available_capacity: Int 19 | var is_removable: Bool 20 | var is_ejectable: Bool 21 | var is_root_filesystem: Bool 22 | 23 | public init(name: String, path: String, total_capacity: Int, available_capacity: Int, is_removable: Bool, is_ejectable: Bool, is_root_filesystem: Bool) { 24 | self.name = SRString(name); 25 | self.path = SRString(path); 26 | self.total_capacity = total_capacity 27 | self.available_capacity = available_capacity 28 | self.is_removable = is_removable 29 | self.is_ejectable = is_ejectable 30 | self.is_root_filesystem = is_root_filesystem 31 | } 32 | } 33 | 34 | @_cdecl("get_mounts") 35 | func getMounts() -> SRObjectArray { 36 | let keys: [URLResourceKey] = [ 37 | .volumeNameKey, 38 | .volumeIsRemovableKey, 39 | .volumeIsEjectableKey, 40 | .volumeTotalCapacityKey, 41 | .volumeAvailableCapacityKey, 42 | .volumeIsRootFileSystemKey, 43 | ] 44 | 45 | let paths = autoreleasepool { 46 | FileManager().mountedVolumeURLs(includingResourceValuesForKeys: keys, options: []) 47 | } 48 | 49 | var validMounts: [Volume] = [] 50 | 51 | if let urls = paths { 52 | autoreleasepool { 53 | for url in urls { 54 | let components = url.pathComponents 55 | if components.count == 1 || components.count > 1 56 | && components[1] == "Volumes" 57 | { 58 | let metadata = try? url.promisedItemResourceValues(forKeys: Set(keys)) 59 | 60 | let volume = Volume( 61 | name: metadata?.volumeName ?? "", 62 | path: url.path, 63 | total_capacity: metadata?.volumeTotalCapacity ?? 0, 64 | available_capacity: metadata?.volumeAvailableCapacity ?? 0, 65 | is_removable: metadata?.volumeIsRemovable ?? false, 66 | is_ejectable: metadata?.volumeIsEjectable ?? false, 67 | is_root_filesystem: metadata?.volumeIsRootFileSystem ?? false 68 | ) 69 | 70 | 71 | validMounts.append(volume) 72 | } 73 | } 74 | } 75 | } 76 | 77 | return SRObjectArray(validMounts) 78 | } 79 | 80 | class Test: NSObject { 81 | var null: Bool 82 | 83 | public init(_ null: Bool) 84 | { 85 | self.null = null; 86 | } 87 | } 88 | 89 | @_cdecl("return_nullable") 90 | func returnNullable(null: Bool) -> Test? { 91 | if (null == true) { return nil } 92 | 93 | return Test(null) 94 | } 95 | -------------------------------------------------------------------------------- /src-rs/types/array.rs: -------------------------------------------------------------------------------- 1 | use std::{ops::Deref, ptr::NonNull}; 2 | 3 | use crate::swift::SwiftObject; 4 | 5 | use super::SRObject; 6 | 7 | /// Wrapper of [`SRArray`] exclusively for arrays of objects. 8 | /// Equivalent to `SRObjectArray` in Swift. 9 | // SRArray is wrapped in SRObject since the Swift implementation extends NSObject 10 | pub type SRObjectArray = SRObject>>; 11 | 12 | #[doc(hidden)] 13 | #[repr(C)] 14 | pub struct SRArrayImpl { 15 | data: NonNull, 16 | length: usize, 17 | } 18 | 19 | /// General array type for objects and scalars. 20 | /// 21 | /// ## Returning Directly 22 | /// 23 | /// When returning an `SRArray` from a Swift function, 24 | /// you will need to wrap it in an `NSObject` class since 25 | /// Swift doesn't permit returning generic types from `@_cdecl` functions. 26 | /// To account for the wrapping `NSObject`, the array must be wrapped 27 | /// in `SRObject` on the Rust side. 28 | /// 29 | /// ```rust 30 | /// use swift_rs::{swift, SRArray, SRObject, Int}; 31 | /// 32 | /// swift!(fn get_int_array() -> SRObject>); 33 | /// 34 | /// let array = unsafe { get_int_array() }; 35 | /// 36 | /// assert_eq!(array.as_slice(), &[1, 2, 3]) 37 | /// ``` 38 | /// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L19) 39 | /// 40 | /// ## Returning in a Struct fIeld 41 | /// 42 | /// When returning an `SRArray` from a custom struct that is itself an `NSObject`, 43 | /// the above work is already done for you. 44 | /// Assuming your custom struct is already wrapped in `SRObject` in Rust, 45 | /// `SRArray` will work normally. 46 | /// 47 | /// ```rust 48 | /// use swift_rs::{swift, SRArray, SRObject, Int}; 49 | /// 50 | /// #[repr(C)] 51 | /// struct ArrayStruct { 52 | /// array: SRArray 53 | /// } 54 | /// 55 | /// swift!(fn get_array_struct() -> SRObject); 56 | /// 57 | /// let data = unsafe { get_array_struct() }; 58 | /// 59 | /// assert_eq!(data.array.as_slice(), &[4, 5, 6]); 60 | /// ``` 61 | /// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L32) 62 | #[repr(transparent)] 63 | pub struct SRArray(SRObject>); 64 | 65 | impl SRArray { 66 | pub fn as_slice(&self) -> &[T] { 67 | self.0.as_slice() 68 | } 69 | } 70 | 71 | impl SwiftObject for SRArray { 72 | type Shape = SRArrayImpl; 73 | 74 | fn get_object(&self) -> &SRObject { 75 | &self.0 76 | } 77 | } 78 | 79 | impl Deref for SRArray { 80 | type Target = [T]; 81 | 82 | fn deref(&self) -> &Self::Target { 83 | self.0.as_slice() 84 | } 85 | } 86 | 87 | impl SRArrayImpl { 88 | pub fn as_slice(&self) -> &[T] { 89 | unsafe { std::slice::from_raw_parts(self.data.as_ref(), self.length) } 90 | } 91 | } 92 | 93 | #[cfg(feature = "serde")] 94 | impl serde::Serialize for SRArray 95 | where 96 | T: serde::Serialize, 97 | { 98 | fn serialize(&self, serializer: S) -> Result 99 | where 100 | S: serde::Serializer, 101 | { 102 | use serde::ser::SerializeSeq; 103 | 104 | let mut seq = serializer.serialize_seq(Some(self.len()))?; 105 | for item in self.iter() { 106 | seq.serialize_element(item)?; 107 | } 108 | seq.end() 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src-rs/swift.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::c_void; 2 | 3 | use crate::*; 4 | 5 | /// Reference to an `NSObject` for internal use by [`swift!`]. 6 | #[must_use = "A Ref MUST be sent over to the Swift side"] 7 | #[repr(transparent)] 8 | pub struct SwiftRef<'a, T: SwiftObject>(&'a SRObjectImpl); 9 | 10 | impl<'a, T: SwiftObject> SwiftRef<'a, T> { 11 | pub(crate) unsafe fn retain(&self) { 12 | retain_object(self.0 as *const _ as *const c_void) 13 | } 14 | } 15 | 16 | /// A type that is represented as an `NSObject` in Swift. 17 | pub trait SwiftObject { 18 | type Shape; 19 | 20 | /// Gets a reference to the `SRObject` at the root of a `SwiftObject` 21 | fn get_object(&self) -> &SRObject; 22 | 23 | /// Creates a [`SwiftRef`] for an object which can be used when calling a Swift function. 24 | /// This function should never be called manually, 25 | /// instead you should rely on the [`swift!`] macro to call it for you. 26 | /// 27 | /// # Safety 28 | /// This function converts the [`NonNull`](std::ptr::NonNull) 29 | /// inside an [`SRObject`] into a reference, 30 | /// implicitly assuming that the pointer is still valid. 31 | /// The inner pointer is private, 32 | /// and the returned [`SwiftRef`] is bound to the lifetime of the original [`SRObject`], 33 | /// so if you use `swift-rs` as normal this function should be safe. 34 | unsafe fn swift_ref(&self) -> SwiftRef 35 | where 36 | Self: Sized, 37 | { 38 | SwiftRef(self.get_object().0.as_ref()) 39 | } 40 | 41 | /// Adds a retain to an object. 42 | /// 43 | /// # Safety 44 | /// Just don't call this, let [`swift!`] handle it for you. 45 | unsafe fn retain(&self) 46 | where 47 | Self: Sized, 48 | { 49 | self.swift_ref().retain() 50 | } 51 | } 52 | 53 | swift!(pub(crate) fn retain_object(obj: *const c_void)); 54 | swift!(pub(crate) fn release_object(obj: *const c_void)); 55 | swift!(pub(crate) fn data_from_bytes(data: *const u8, size: Int) -> SRData); 56 | swift!(pub(crate) fn string_from_bytes(data: *const u8, size: Int) -> SRString); 57 | 58 | /// Declares a function defined in a swift library. 59 | /// As long as this macro is used, retain counts of arguments 60 | /// and return values will be correct. 61 | /// 62 | /// Use this macro as if the contents were going directly 63 | /// into an `extern "C"` block. 64 | /// 65 | /// ``` 66 | /// use swift_rs::*; 67 | /// 68 | /// swift!(fn echo(string: &SRString) -> SRString); 69 | /// 70 | /// let string: SRString = "test".into(); 71 | /// let result = unsafe { echo(&string) }; 72 | /// 73 | /// assert_eq!(result.as_str(), string.as_str()) 74 | /// ``` 75 | /// 76 | /// # Details 77 | /// 78 | /// Internally this macro creates a wrapping function around an `extern "C"` block 79 | /// that represents the actual Swift function. This is done in order to restrict the types 80 | /// that can be used as arguments and return types, and to ensure that retain counts of returned 81 | /// values are appropriately balanced. 82 | #[macro_export] 83 | macro_rules! swift { 84 | ($vis:vis fn $name:ident $(<$($lt:lifetime),+>)? ($($arg:ident: $arg_ty:ty),*) $(-> $ret:ty)?) => { 85 | $vis unsafe fn $name $(<$($lt),*>)? ($($arg: $arg_ty),*) $(-> $ret)? { 86 | extern "C" { 87 | fn $name $(<$($lt),*>)? ($($arg: <$arg_ty as $crate::SwiftArg>::ArgType),*) $(-> $ret)?; 88 | } 89 | 90 | let res = { 91 | $(let $arg = $crate::SwiftArg::as_arg(&$arg);)* 92 | 93 | $name($($arg),*) 94 | }; 95 | 96 | $crate::SwiftRet::retain(&res); 97 | 98 | res 99 | } 100 | }; 101 | } 102 | -------------------------------------------------------------------------------- /tests/test_bindings.rs: -------------------------------------------------------------------------------- 1 | //! Test for swift-rs bindings 2 | //! 3 | //! Needs to be run with the env var `TEST_SWIFT_RS=true`, to allow for 4 | //! the test swift code to be linked. 5 | 6 | use serial_test::serial; 7 | use std::{env, process::Command}; 8 | use swift_rs::*; 9 | 10 | macro_rules! test_with_leaks { 11 | ( $op:expr ) => {{ 12 | let leaks_env_var = "TEST_RUNNING_UNDER_LEAKS"; 13 | if env::var(leaks_env_var).unwrap_or_else(|_| "false".into()) == "true" { 14 | let _ = $op(); 15 | } else { 16 | // we run $op directly in the current process first, as leaks will not give 17 | // us the exit code of $op, but only if memory leaks happened or not 18 | $op(); 19 | 20 | // and now we run the above codepath under leaks monitoring 21 | let exe = env::current_exe().unwrap(); 22 | 23 | // codesign the binary first, so that leaks can be run 24 | let debug_plist = exe.parent().unwrap().join("debug.plist"); 25 | let plist_path = &debug_plist.to_string_lossy(); 26 | std::fs::write(&debug_plist, DEBUG_PLIST_XML.as_bytes()).unwrap(); 27 | let status = Command::new("codesign") 28 | .args([ 29 | "-s", 30 | "-", 31 | "-v", 32 | "-f", 33 | "--entitlements", 34 | plist_path, 35 | &exe.to_string_lossy(), 36 | ]) 37 | .status() 38 | .expect("cmd failure"); 39 | assert!(status.success(), "failed to codesign"); 40 | 41 | // run leaks command to detect memory leaks 42 | let status = Command::new("leaks") 43 | .args(["-atExit", "--", &exe.to_string_lossy(), "--nocapture"]) 44 | .env(leaks_env_var, "true") 45 | .status() 46 | .expect("cmd failure"); 47 | assert!(status.success(), "leaks detected in memory pressure test"); 48 | } 49 | }}; 50 | } 51 | 52 | swift!(fn echo(string: &SRString) -> SRString); 53 | 54 | #[test] 55 | #[serial] 56 | fn test_reflection() { 57 | test_with_leaks!(|| { 58 | // create memory pressure 59 | let name: SRString = "Brendan".into(); 60 | for _ in 0..10_000 { 61 | let reflected = unsafe { echo(&name) }; 62 | assert_eq!(name.as_str(), reflected.as_str()); 63 | } 64 | }); 65 | } 66 | 67 | swift!(fn get_greeting(name: &SRString) -> SRString); 68 | 69 | #[test] 70 | #[serial] 71 | fn test_string() { 72 | test_with_leaks!(|| { 73 | let name: SRString = "Brendan".into(); 74 | let greeting = unsafe { get_greeting(&name) }; 75 | assert_eq!(greeting.as_str(), "Hello Brendan!"); 76 | }); 77 | } 78 | 79 | #[test] 80 | #[serial] 81 | fn test_memory_pressure() { 82 | test_with_leaks!(|| { 83 | // create memory pressure 84 | let name: SRString = "Brendan".into(); 85 | for _ in 0..10_000 { 86 | let greeting = unsafe { get_greeting(&name) }; 87 | assert_eq!(greeting.as_str(), "Hello Brendan!"); 88 | } 89 | }); 90 | } 91 | 92 | #[test] 93 | #[serial] 94 | fn test_autoreleasepool() { 95 | test_with_leaks!(|| { 96 | // create memory pressure 97 | let name: SRString = "Brendan".into(); 98 | for _ in 0..10_000 { 99 | autoreleasepool!({ 100 | let greeting = unsafe { get_greeting(&name) }; 101 | assert_eq!(greeting.as_str(), "Hello Brendan!"); 102 | }); 103 | } 104 | }); 105 | } 106 | 107 | #[repr(C)] 108 | struct Complex { 109 | a: SRString, 110 | b: Int, 111 | c: Bool, 112 | } 113 | 114 | swift!(fn complex_data() -> SRObjectArray); 115 | 116 | #[test] 117 | #[serial] 118 | fn test_complex() { 119 | test_with_leaks!(|| { 120 | let mut v = vec![]; 121 | 122 | for _ in 0..10_000 { 123 | let data = unsafe { complex_data() }; 124 | assert_eq!(data[0].a.as_str(), "Brendan"); 125 | v.push(data); 126 | } 127 | }); 128 | } 129 | 130 | swift!(fn echo_data(data: &SRData) -> SRData); 131 | 132 | #[test] 133 | #[serial] 134 | fn test_data() { 135 | test_with_leaks!(|| { 136 | let str: &str = "hello"; 137 | let bytes = str.as_bytes(); 138 | for _ in 0..10_000 { 139 | let data = unsafe { echo_data(&bytes.into()) }; 140 | assert_eq!(data.as_slice(), bytes); 141 | } 142 | }); 143 | } 144 | 145 | const DEBUG_PLIST_XML: &str = r#" 146 | 147 | 148 | com.apple.security.get-task-allow 149 | 150 | "#; 151 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2023 The swift-rs developers 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src-rs/build.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use std::{env, fmt::Display, path::Path, path::PathBuf, process::Command}; 3 | 4 | use serde::Deserialize; 5 | 6 | #[derive(Debug, Deserialize)] 7 | #[serde(rename_all = "camelCase")] 8 | struct SwiftTarget { 9 | triple: String, 10 | unversioned_triple: String, 11 | module_triple: String, 12 | //pub swift_runtime_compatibility_version: String, 13 | #[serde(rename = "librariesRequireRPath")] 14 | libraries_require_rpath: bool, 15 | } 16 | 17 | #[derive(Debug, Deserialize)] 18 | #[serde(rename_all = "camelCase")] 19 | struct SwiftPaths { 20 | runtime_library_paths: Vec, 21 | runtime_library_import_paths: Vec, 22 | runtime_resource_path: String, 23 | } 24 | 25 | #[derive(Deserialize)] 26 | struct SwiftEnv { 27 | target: SwiftTarget, 28 | paths: SwiftPaths, 29 | } 30 | 31 | impl SwiftEnv { 32 | fn new( 33 | minimum_macos_version: &str, 34 | minimum_ios_version: Option<&str>, 35 | minimum_visionos_version: Option<&str>, 36 | ) -> Self { 37 | let rust_target = RustTarget::from_env(); 38 | let target = rust_target.swift_target_triple( 39 | minimum_macos_version, 40 | minimum_ios_version, 41 | minimum_visionos_version, 42 | ); 43 | 44 | let swift_target_info_str = Command::new("swift") 45 | .args(["-target", &target, "-print-target-info"]) 46 | .output() 47 | .unwrap() 48 | .stdout; 49 | 50 | serde_json::from_slice(&swift_target_info_str).unwrap() 51 | } 52 | } 53 | 54 | #[allow(clippy::upper_case_acronyms)] 55 | enum RustTargetOS { 56 | MacOS, 57 | IOS, 58 | VisionOS, 59 | } 60 | 61 | impl RustTargetOS { 62 | fn from_env() -> Self { 63 | match env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() { 64 | "macos" => RustTargetOS::MacOS, 65 | "ios" => RustTargetOS::IOS, 66 | "visionos" => RustTargetOS::VisionOS, 67 | _ => panic!("unexpected target operating system"), 68 | } 69 | } 70 | 71 | fn to_swift(&self) -> &'static str { 72 | match self { 73 | Self::MacOS => "macosx", 74 | Self::IOS => "ios", 75 | Self::VisionOS => "xros", 76 | } 77 | } 78 | } 79 | 80 | impl Display for RustTargetOS { 81 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 82 | match self { 83 | Self::MacOS => write!(f, "macos"), 84 | Self::IOS => write!(f, "ios"), 85 | Self::VisionOS => write!(f, "visionos"), 86 | } 87 | } 88 | } 89 | 90 | #[allow(clippy::upper_case_acronyms)] 91 | enum SwiftSDK { 92 | MacOS, 93 | IOS, 94 | IOSSimulator, 95 | VisionOS, 96 | VisionOSSimulator, 97 | } 98 | 99 | impl SwiftSDK { 100 | fn from_os(os: &RustTargetOS) -> Self { 101 | let target = env::var("TARGET").unwrap(); 102 | let simulator = target.ends_with("ios-sim") 103 | || target.ends_with("visionos-sim") 104 | || (target.starts_with("x86_64") && target.ends_with("ios")); 105 | 106 | match os { 107 | RustTargetOS::MacOS => Self::MacOS, 108 | RustTargetOS::IOS if simulator => Self::IOSSimulator, 109 | RustTargetOS::IOS => Self::IOS, 110 | RustTargetOS::VisionOS if simulator => Self::VisionOSSimulator, 111 | RustTargetOS::VisionOS => Self::VisionOS, 112 | } 113 | } 114 | 115 | fn clang_lib_extension(&self) -> &'static str { 116 | match self { 117 | Self::MacOS => "osx", 118 | Self::IOS => "ios", 119 | Self::IOSSimulator => "iossim", 120 | Self::VisionOS => "xros", 121 | Self::VisionOSSimulator => "xrsimulator", 122 | } 123 | } 124 | } 125 | 126 | impl Display for SwiftSDK { 127 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 128 | match self { 129 | Self::MacOS => write!(f, "macosx"), 130 | Self::IOSSimulator => write!(f, "iphonesimulator"), 131 | Self::IOS => write!(f, "iphoneos"), 132 | Self::VisionOSSimulator => write!(f, "xrsimulator"), 133 | Self::VisionOS => write!(f, "xros"), 134 | } 135 | } 136 | } 137 | 138 | struct RustTarget { 139 | arch: String, 140 | os: RustTargetOS, 141 | sdk: SwiftSDK, 142 | } 143 | 144 | impl RustTarget { 145 | fn from_env() -> Self { 146 | let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); 147 | let os = RustTargetOS::from_env(); 148 | let sdk = SwiftSDK::from_os(&os); 149 | 150 | Self { arch, os, sdk } 151 | } 152 | 153 | fn swift_target_triple( 154 | &self, 155 | minimum_macos_version: &str, 156 | minimum_ios_version: Option<&str>, 157 | minimum_visionos_version: Option<&str>, 158 | ) -> String { 159 | let unversioned = self.unversioned_swift_target_triple(); 160 | format!( 161 | "{unversioned}{}{}", 162 | match &self.os { 163 | RustTargetOS::MacOS => minimum_macos_version, 164 | RustTargetOS::IOS => minimum_ios_version.unwrap(), 165 | RustTargetOS::VisionOS => minimum_visionos_version.unwrap(), 166 | }, 167 | // simulator suffix 168 | matches!( 169 | self.sdk, 170 | SwiftSDK::IOSSimulator | SwiftSDK::VisionOSSimulator 171 | ) 172 | .then(|| "-simulator".to_string()) 173 | .unwrap_or_default() 174 | ) 175 | } 176 | 177 | fn unversioned_swift_target_triple(&self) -> String { 178 | format!( 179 | "{}-apple-{}", 180 | match self.arch.as_str() { 181 | "aarch64" => "arm64", 182 | a => a, 183 | }, 184 | self.os.to_swift(), 185 | ) 186 | } 187 | } 188 | 189 | struct SwiftPackage { 190 | name: String, 191 | path: PathBuf, 192 | } 193 | 194 | /// Builder for linking the Swift runtime and custom packages. 195 | #[cfg(feature = "build")] 196 | pub struct SwiftLinker { 197 | packages: Vec, 198 | macos_min_version: String, 199 | ios_min_version: Option, 200 | visionos_min_version: Option, 201 | } 202 | 203 | impl SwiftLinker { 204 | /// Creates a new [`SwiftLinker`] with a minimum macOS verison. 205 | /// 206 | /// Minimum macOS version must be at least 10.13. 207 | pub fn new(macos_min_version: &str) -> Self { 208 | Self { 209 | packages: vec![], 210 | macos_min_version: macos_min_version.to_string(), 211 | ios_min_version: None, 212 | visionos_min_version: None, 213 | } 214 | } 215 | 216 | /// Instructs the [`SwiftLinker`] to also compile for iOS 217 | /// using the specified minimum iOS version. 218 | /// 219 | /// Minimum iOS version must be at least 11. 220 | pub fn with_ios(mut self, min_version: &str) -> Self { 221 | self.ios_min_version = Some(min_version.to_string()); 222 | self 223 | } 224 | 225 | /// Instructs the [`SwiftLinker`] to also compile for visionOS 226 | /// using the specified minimum visionOS version. 227 | /// 228 | /// Minimum visionOS version must be at least 11. 229 | pub fn with_visionos(mut self, min_version: &str) -> Self { 230 | self.visionos_min_version = Some(min_version.to_string()); 231 | self 232 | } 233 | 234 | /// Adds a package to be linked against. 235 | /// `name` should match the `name` field in your `Package.swift`, 236 | /// and `path` should point to the root of your Swift package relative 237 | /// to your crate's root. 238 | pub fn with_package(mut self, name: &str, path: impl AsRef) -> Self { 239 | self.packages.extend([SwiftPackage { 240 | name: name.to_string(), 241 | path: path.as_ref().into(), 242 | }]); 243 | 244 | self 245 | } 246 | 247 | /// Links the Swift runtime, then builds and links the provided packages. 248 | /// This does not (yet) automatically rebuild your Swift files when they are modified, 249 | /// you'll need to modify/save your `build.rs` file for that. 250 | pub fn link(self) { 251 | let swift_env = SwiftEnv::new( 252 | &self.macos_min_version, 253 | self.ios_min_version.as_deref(), 254 | self.visionos_min_version.as_deref(), 255 | ); 256 | 257 | #[allow(clippy::uninlined_format_args)] 258 | for path in swift_env.paths.runtime_library_paths { 259 | println!("cargo:rustc-link-search=native={path}"); 260 | } 261 | 262 | let debug = env::var("DEBUG").unwrap() == "true"; 263 | let configuration = if debug { "debug" } else { "release" }; 264 | let rust_target = RustTarget::from_env(); 265 | 266 | link_clang_rt(&rust_target); 267 | 268 | for package in self.packages { 269 | let package_path = 270 | Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join(&package.path); 271 | let out_path = Path::new(&env::var("OUT_DIR").unwrap()) 272 | .join("swift-rs") 273 | .join(&package.name); 274 | 275 | let sdk_path_output = Command::new("xcrun") 276 | .args(["--sdk", &rust_target.sdk.to_string(), "--show-sdk-path"]) 277 | .output() 278 | .unwrap(); 279 | if !sdk_path_output.status.success() { 280 | panic!( 281 | "Failed to get SDK path with `xcrun --sdk {} --show-sdk-path`", 282 | rust_target.sdk 283 | ); 284 | } 285 | 286 | let sdk_path = String::from_utf8_lossy(&sdk_path_output.stdout); 287 | 288 | let mut command = Command::new("swift"); 289 | command.current_dir(&package.path); 290 | 291 | let arch = match std::env::consts::ARCH { 292 | "aarch64" => "arm64", 293 | arch => arch, 294 | }; 295 | 296 | let swift_target_triple = rust_target.swift_target_triple( 297 | &self.macos_min_version, 298 | self.ios_min_version.as_deref(), 299 | self.visionos_min_version.as_deref(), 300 | ); 301 | 302 | command 303 | // Build the package (duh) 304 | .arg("build") 305 | // SDK path for regular compilation (idk) 306 | .args(["--sdk", sdk_path.trim()]) 307 | // Release/Debug configuration 308 | .args(["-c", configuration]) 309 | .args(["--arch", arch]) 310 | // Where the artifacts will be generated to 311 | .args(["--build-path", &out_path.display().to_string()]) 312 | // Override SDK path for each swiftc instance. 313 | // Necessary for iOS compilation. 314 | .args(["-Xswiftc", "-sdk"]) 315 | .args(["-Xswiftc", sdk_path.trim()]) 316 | // Override target triple for each swiftc instance. 317 | // Necessary for iOS compilation. 318 | .args(["-Xswiftc", "-target"]) 319 | .args(["-Xswiftc", &swift_target_triple]) 320 | .args(["-Xcc", &format!("--target={swift_target_triple}")]) 321 | .args(["-Xcxx", &format!("--target={swift_target_triple}")]); 322 | 323 | println!("Command `{command:?}`"); 324 | 325 | if !command.status().unwrap().success() { 326 | panic!("Failed to compile swift package {}", package.name); 327 | } 328 | 329 | let search_path = out_path 330 | // swift build uses this output folder no matter what is the target 331 | .join(format!("{}-apple-macosx", arch)) 332 | .join(configuration); 333 | 334 | println!("cargo:rerun-if-changed={}", package_path.display()); 335 | println!("cargo:rustc-link-search=native={}", search_path.display()); 336 | println!("cargo:rustc-link-lib=static={}", package.name); 337 | } 338 | } 339 | } 340 | 341 | fn link_clang_rt(rust_target: &RustTarget) { 342 | println!( 343 | "cargo:rustc-link-lib=clang_rt.{}", 344 | rust_target.sdk.clang_lib_extension() 345 | ); 346 | println!("cargo:rustc-link-search={}", clang_link_search_path()); 347 | } 348 | 349 | fn clang_link_search_path() -> String { 350 | let output = std::process::Command::new( 351 | std::env::var("SWIFT_RS_CLANG").unwrap_or_else(|_| "/usr/bin/clang".to_string()), 352 | ) 353 | .arg("--print-search-dirs") 354 | .output() 355 | .unwrap(); 356 | if !output.status.success() { 357 | panic!("Can't get search paths from clang"); 358 | } 359 | let stdout = String::from_utf8_lossy(&output.stdout); 360 | for line in stdout.lines() { 361 | if line.contains("libraries: =") { 362 | let path = line.split('=').nth(1).unwrap(); 363 | return format!("{}/lib/darwin", path); 364 | } 365 | } 366 | panic!("clang is missing search paths"); 367 | } 368 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swift-rs 2 | 3 | ![Crates.io](https://img.shields.io/crates/v/swift-rs?color=blue&style=flat-square) 4 | ![docs.rs](https://img.shields.io/docsrs/swift-rs?color=blue&style=flat-square) 5 | 6 | Call Swift functions from Rust with ease! 7 | 8 | ## Setup 9 | 10 | Add `swift-rs` to your project's `dependencies` and `build-dependencies`: 11 | 12 | ```toml 13 | [dependencies] 14 | swift-rs = "1.0.5" 15 | 16 | [build-dependencies] 17 | swift-rs = { version = "1.0.5", features = ["build"] } 18 | ``` 19 | 20 | Next, some setup work must be done: 21 | 22 | 1. Ensure your swift code is organized into a Swift Package. 23 | This can be done in XCode by selecting File -> New -> Project -> Multiplatform -> Swift Package and importing your existing code. 24 | 2. Add `SwiftRs` as a dependency to your Swift package and make the build type `.static`. 25 | ```swift 26 | let package = Package( 27 | dependencies: [ 28 | .package(url: "https://github.com/Brendonovich/swift-rs", from: "1.0.5") 29 | ], 30 | products: [ 31 | .library( 32 | type: .static, 33 | ), 34 | ], 35 | targets: [ 36 | .target( 37 | // Must specify swift-rs as a dependency of your target 38 | dependencies: [ 39 | .product( 40 | name: "SwiftRs", 41 | package: "swift-rs" 42 | ) 43 | ], 44 | ) 45 | ] 46 | ) 47 | ``` 48 | 3. Create a `build.rs` file in your project's root folder, if you don't have one already. 49 | 4. Use `SwiftLinker` in your `build.rs` file to link both the Swift runtime and your Swift package. 50 | The package name should be the same as is specified in your `Package.swift` file, 51 | and the path should point to your Swift project's root folder relative to your crate's root folder. 52 | 53 | ```rust 54 | use swift_rs::SwiftLinker; 55 | 56 | fn build() { 57 | // swift-rs has a minimum of macOS 10.13 58 | // Ensure the same minimum supported macOS version is specified as in your `Package.swift` file. 59 | SwiftLinker::new("10.13") 60 | // Only if you are also targetting iOS 61 | // Ensure the same minimum supported iOS version is specified as in your `Package.swift` file 62 | .with_ios("11") 63 | .with_package(PACKAGE_NAME, PACKAGE_PATH) 64 | .link(); 65 | 66 | // Other build steps 67 | } 68 | ``` 69 | 70 | With those steps completed, you should be ready to start using Swift code from Rust! 71 | 72 | If you experience the error `dyld[16008]: Library not loaded: @rpath/libswiftCore.dylib` 73 | when using `swift-rs` with [Tauri](https://tauri.app) ensure you have set your 74 | [Tauri minimum system version](https://tauri.app/v1/guides/building/macos#setting-a-minimum-system-version) 75 | to `10.15` or higher in your `tauri.config.json`. 76 | 77 | ## Calling basic functions 78 | 79 | To allow calling a Swift function from Rust, it must follow some rules: 80 | 81 | 1. It must be global 82 | 2. It must be annotated with `@_cdecl`, so that it is callable from C 83 | 3. It must only use types that can be represented in Objective-C, 84 | so only classes that derive `NSObject`, as well as scalars such as Int and Bool. 85 | This excludes strings, arrays, generics (though all of these can be sent with workarounds) 86 | and structs (which are strictly forbidden). 87 | 88 | For this example we will use a function that simply squares a number: 89 | 90 | ```swift 91 | public func squareNumber(number: Int) -> Int { 92 | return number * number 93 | } 94 | ``` 95 | 96 | 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. 97 | However, it is not annotated with `@_cdecl`. 98 | 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. 99 | To keep with Rust's naming conventions, we will export this function in snake case as `square_number`. 100 | 101 | ```swift 102 | @_cdecl("square_number") 103 | public func squareNumber(number: Int) -> Int { 104 | return number * number 105 | } 106 | ``` 107 | 108 | Now that `squareNumber` is properly exposed to Rust, we can start interfacing with it. 109 | This can be done using the `swift!` macro, with the `Int` type helping to provide a similar function signature: 110 | 111 | ```rust 112 | use swift_rs::swift; 113 | 114 | swift!(fn square_number(number: Int) -> Int); 115 | ``` 116 | 117 | Lastly, you can call the function from regular Rust functions. 118 | Note that all calls to a Swift function are unsafe, 119 | and require wrapping in an `unsafe {}` block or `unsafe fn`. 120 | 121 | ```rust 122 | fn main() { 123 | let input: Int = 4; 124 | let output = unsafe { square_number(input) }; 125 | 126 | println!("Input: {}, Squared: {}", input, output); 127 | // Prints "Input: 4, Squared: 16" 128 | } 129 | ``` 130 | 131 | Check [the documentation](TODO) for all available helper types. 132 | 133 | ## Returning objects from Swift 134 | 135 | Let's say that we want our `squareNumber` function to return not only the result, but also the original input. 136 | A standard way to do this in Swift would be with a struct: 137 | 138 | ```swift 139 | struct SquareNumberResult { 140 | var input: Int 141 | var output: Int 142 | } 143 | ``` 144 | 145 | We are not allowed to do this, though, since structs cannot be represented in Objective-C. 146 | Instead, we must use a class that extends `NSObject`: 147 | 148 | ```swift 149 | class SquareNumberResult: NSObject { 150 | var input: Int 151 | var output: Int 152 | 153 | init(_ input: Int, _ output: Int) { 154 | self.input = input; 155 | self.output = output 156 | } 157 | } 158 | ``` 159 | 160 | Yes, this class could contain the squaring logic too, but that is irrelevant for this example 161 | 162 | An instance of this class can then be returned from `squareNumber`: 163 | 164 | ```swift 165 | @_cdecl("square_number") 166 | public func squareNumber(input: Int) -> SquareNumberResult { 167 | let output = input * input 168 | return SquareNumberResult(input, output) 169 | } 170 | ``` 171 | 172 | As you can see, returning an `NSObject` from Swift isn't too difficult. 173 | The same can't be said for the Rust implementation, though. 174 | `squareNumber` doesn't actually return a struct containing `input` and `output`, 175 | but instead a pointer to a `SquareNumberResult` stored somewhere in memory. 176 | Additionally, this value contains more data than just `input` and `output`: 177 | Since it is an `NSObject`, it contains extra data that must be accounted for when using it in Rust. 178 | 179 | This may sound daunting, but it's not actually a problem thanks to `SRObject`. 180 | This type manages the pointer internally, and takes a generic argument for a struct that we can access the data through. 181 | Let's see how we'd implement `SquareNumberResult` in Rust: 182 | 183 | ```rust 184 | use swift_rs::{swift, Int, SRObject}; 185 | 186 | // Any struct that is used in a C function must be annotated 187 | // with this, and since our Swift function is exposed as a 188 | // C function with @_cdecl, this is necessary here 189 | #[repr(C)] 190 | // Struct matches the class declaration in Swift 191 | struct SquareNumberResult { 192 | input: Int, 193 | output: Int 194 | } 195 | 196 | // SRObject abstracts away the underlying pointer and will automatically deref to 197 | // &SquareNumberResult through the Deref trait 198 | swift!(fn square_number(input: Int) -> SRObject); 199 | ``` 200 | 201 | Then, using the new return value is just like using `SquareNumberResult` directly: 202 | 203 | ```rust 204 | fn main() { 205 | let input = 4; 206 | let result = unsafe { square_number(input) }; 207 | 208 | let result_input = result.input; // 4 209 | let result_output = result.output; // 16 210 | } 211 | ``` 212 | 213 | Creating objects in Rust and then passing them to Swift is not supported. 214 | 215 | ## Optionals 216 | 217 | `swift-rs` also supports Swift's `nil` type, but only for functions that return optional `NSObject`s. 218 | Functions returning optional primitives cannot be represented in Objective C, and thus are not supported. 219 | 220 | Let's say we have a function returning an optional `SRString`: 221 | 222 | ```swift 223 | @_cdecl("optional_string") 224 | func optionalString(returnNil: Bool) -> SRString? { 225 | if (returnNil) return nil 226 | else return SRString("lorem ipsum") 227 | } 228 | ``` 229 | 230 | Thanks to Rust's [null pointer optimisation](https://doc.rust-lang.org/std/option/index.html#representation), 231 | the optional nature of `SRString?` can be represented by wrapping `SRString` in Rust's `Option` type! 232 | 233 | ```rust 234 | use swift_rs::{swift, Bool, SRString}; 235 | 236 | swift!(optional_string(return_nil: Bool) -> Option) 237 | ``` 238 | 239 | Null pointers are actually the reason why a function that returns an optional primitive cannot be represented in C. 240 | If this were to be supported, how could a `nil` be differentiated from a number? It can't! 241 | 242 | ## Complex types 243 | 244 | So far we have only looked at using primitive types and structs/classes, 245 | but this leaves out some of the most important data structures: arrays (`SRArray`) and strings (`SRString`). 246 | These types must be treated with caution, however, and are not as flexible as their native Swift & Rust counterparts. 247 | 248 | ### Strings 249 | 250 | Strings can be passed between Rust and Swift through `SRString`, which can be created from native strings in either language. 251 | 252 | **As an argument** 253 | 254 | ```swift 255 | import SwiftRs 256 | 257 | @_cdecl("swift_print") 258 | public func swiftPrint(value: SRString) { 259 | // .to_string() converts the SRString to a Swift String 260 | print(value.to_string()) 261 | } 262 | ``` 263 | 264 | ```rust 265 | use swift_rs::{swift, SRString, SwiftRef}; 266 | 267 | swift!(fn swift_print(value: &SRString)); 268 | 269 | fn main() { 270 | // SRString can be created by simply calling .into() on any string reference. 271 | // This will allocate memory in Swift and copy the string 272 | let value: SRString = "lorem ipsum".into(); 273 | 274 | unsafe { swift_print(&value) }; // Will print "lorem ipsum" to the console 275 | } 276 | ``` 277 | 278 | **As a return value** 279 | 280 | ```swift 281 | import SwiftRs 282 | 283 | @_cdecl("get_string") 284 | public func getString() -> SRString { 285 | let value = "lorem ipsum" 286 | 287 | // SRString can be created from a regular String 288 | return SRString(value) 289 | } 290 | ``` 291 | 292 | ```rust 293 | use swift_rs::{swift, SRString}; 294 | 295 | swift!(fn get_string() -> SRString); 296 | 297 | fn main() { 298 | let value_srstring = unsafe { get_string() }; 299 | 300 | // SRString can be converted to an &str using as_str()... 301 | let value_str: &str = value_srstring.as_str(); 302 | // or though the Deref trait 303 | let value_str: &str = &*value_srstring; 304 | 305 | // SRString also implements Display 306 | println!("{}", value_srstring); // Will print "lorem ipsum" to the console 307 | } 308 | ``` 309 | 310 | ### Arrays 311 | 312 | **Primitive Arrays** 313 | 314 | Representing arrays properly is tricky, since we cannot use generics as Swift arguments or return values according to rule 3. 315 | Instead, `swift-rs` provides a generic `SRArray` that can be embedded inside another class that extends `NSObject` that is not generic, 316 | but is restricted to a single element type. 317 | 318 | ```swift 319 | import SwiftRs 320 | 321 | // Argument/Return values can contain generic types, but cannot be generic themselves. 322 | // This includes extending generic types. 323 | class IntArray: NSObject { 324 | var data: SRArray 325 | 326 | init(_ data: [Int]) { 327 | self.data = SRArray(data) 328 | } 329 | } 330 | 331 | @_cdecl("get_numbers") 332 | public func getNumbers() -> IntArray { 333 | let numbers = [1, 2, 3, 4] 334 | 335 | return IntArray(numbers) 336 | } 337 | ``` 338 | 339 | ```rust 340 | use swift_rs::{Int, SRArray, SRObject}; 341 | 342 | #[repr(C)] 343 | struct IntArray { 344 | data: SRArray 345 | } 346 | 347 | // Since IntArray extends NSObject in its Swift implementation, 348 | // it must be wrapped in SRObject on the Rust side 349 | swift!(fn get_numbers() -> SRObject); 350 | 351 | fn main() { 352 | let numbers = unsafe { get_numbers() }; 353 | 354 | // SRArray can be accessed as a slice via as_slice 355 | let numbers_slice: &[Int] = numbers.data.as_slice(); 356 | 357 | assert_eq!(numbers_slice, &[1, 2, 3, 4]); 358 | } 359 | ``` 360 | 361 | To simplify things on the rust side, we can actually do away with the `IntArray` struct. 362 | Since `IntArray` only has one field, its memory layout is identical to that of `SRArray`, 363 | so our Rust implementation can be simplified at the cost of equivalence with our Swift code: 364 | 365 | ```rust 366 | // We still need to wrap the array in SRObject since 367 | // the wrapper class in Swift is an NSObject 368 | swift!(fn get_numbers() -> SRObject>); 369 | ``` 370 | 371 | **NSObject Arrays** 372 | 373 | What if we want to return an `NSObject` array? There are two options on the Swift side: 374 | 375 | 1. Continue using `SRArray` and a custom wrapper type, or 376 | 2. Use `SRObjectArray`, a wrapper type provided by `swift-rs` that accepts any `NSObject` as its elements. 377 | This can be easier than continuing to create wrapper types, but sacrifices some type safety. 378 | 379 | There is also `SRObjectArray` for Rust, which is compatible with any single-element Swift wrapper type (and of course `SRObjectArray` in Swift), 380 | and automatically wraps its elements in `SRObject`, so there's very little reason to not use it unless you _really_ like custom wrapper types. 381 | 382 | Using `SRObjectArray` in both Swift and Rust with a basic custom class/struct can be done like this: 383 | 384 | ```swift 385 | import SwiftRs 386 | 387 | class IntTuple: NSObject { 388 | var item1: Int 389 | var item2: Int 390 | 391 | init(_ item1: Int, _ item2: Int) { 392 | self.item1 = item1 393 | self.item2 = item2 394 | } 395 | } 396 | 397 | @_cdecl("get_tuples") 398 | public func getTuples() -> SRObjectArray { 399 | let tuple1 = IntTuple(0,1), 400 | tuple2 = IntTuple(2,3), 401 | tuple3 = IntTuple(4,5) 402 | 403 | let tupleArray: [IntTuple] = [ 404 | tuple1, 405 | tuple2, 406 | tuple3 407 | ] 408 | 409 | // Type safety is only lost when the Swift array is converted to an SRObjectArray 410 | return SRObjectArray(tupleArray) 411 | } 412 | ``` 413 | 414 | ```rust 415 | use swift_rs::{swift, Int, SRObjectArray}; 416 | 417 | #[repr(C)] 418 | struct IntTuple { 419 | item1: Int, 420 | item2: Int 421 | } 422 | 423 | // No need to wrap IntTuple in SRObject since 424 | // SRObjectArray does it automatically 425 | swift!(fn get_tuples() -> SRObjectArray); 426 | 427 | fn main() { 428 | let tuples = unsafe { get_tuples() }; 429 | 430 | for tuple in tuples.as_slice() { 431 | // Will print each tuple's contents to the console 432 | println!("Item 1: {}, Item 2: {}", tuple.item1, tuple.item2); 433 | } 434 | } 435 | ``` 436 | 437 | Complex types can contain whatever combination of primitives and `SRObject` you like, just remember to follow the 3 rules! 438 | 439 | ## Bonuses 440 | 441 | ### SRData 442 | 443 | A wrapper type for `SRArray` designed for storing `u8`s - essentially just a byte buffer. 444 | 445 | ### Tighter Memory Control with `autoreleasepool!` 446 | 447 | If you've come to Swift from an Objective-C background, you likely know the utility of `@autoreleasepool` blocks. 448 | `swift-rs` has your back on this too, just wrap your block of code with a `autoreleasepool!`, and that block of code now executes with its own autorelease pool! 449 | 450 | ```rust 451 | use swift_rs::autoreleasepool; 452 | 453 | for _ in 0..10000 { 454 | autoreleasepool!({ 455 | // do some memory intensive thing here 456 | }); 457 | } 458 | ``` 459 | 460 | ## Limitations 461 | 462 | Currently, the only types that can be created from Rust are number types, boolean, `SRString`, and `SRData`. 463 | This is because those types are easy to allocate memory for, either on the stack or on the heap via calling out to swift, 464 | whereas other types are not. This may be implemented in the future, though. 465 | 466 | Mutating values across Swift and Rust is not currently an aim for this library, it is purely for providing arguments and returning values. 467 | 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. 468 | 469 | ## License 470 | 471 | Licensed under either of 472 | 473 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 474 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 475 | 476 | at your option. 477 | 478 | ### Contribution 479 | 480 | Unless you explicitly state otherwise, any contribution intentionally 481 | submitted for inclusion in the work by you, as defined in the Apache-2.0 482 | license, shall be dual licensed as above, without any additional terms or 483 | conditions. 484 | --------------------------------------------------------------------------------