diff --git a/swift-rs/.gitattributes b/swift-rs/.gitattributes
new file mode 100644
index 0000000..0bad51c
--- /dev/null
+++ b/swift-rs/.gitattributes
@@ -0,0 +1 @@
+example/* linguist-vendored
diff --git a/swift-rs/.github/workflows/main.yaml b/swift-rs/.github/workflows/main.yaml
new file mode 100644
index 0000000..3ce7119
--- /dev/null
+++ b/swift-rs/.github/workflows/main.yaml
@@ -0,0 +1,38 @@
+name: Build
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+
+env:
+ CARGO_TERM_COLOR: always
+ RUST_BACKTRACE: 1
+
+jobs:
+ build:
+ name: Build
+ runs-on: macos-latest
+ strategy:
+ matrix:
+ rust: [stable, beta]
+ steps:
+ - uses: actions/checkout@v3
+ name: Checkout
+ - name: Install specific rust version
+ run: |
+ rustup install ${{ matrix.rust }} --profile minimal
+ rustup component add --toolchain ${{ matrix.rust }} rustfmt clippy
+ - name: Setup cache
+ uses: Swatinem/rust-cache@v2
+ - name: Test example
+ working-directory: example
+ run: cargo +${{ matrix.rust }} run
+ - name: Run Tests
+ env:
+ TEST_SWIFT_RS: "true"
+ run: cargo +${{ matrix.rust }} test --features build
+ - name: Check Code Formatting
+ run: cargo +${{ matrix.rust }} fmt --all -- --check
+ - name: Lints
+ run: cargo +${{ matrix.rust }} clippy -- -D warnings
diff --git a/swift-rs/.gitignore b/swift-rs/.gitignore
new file mode 100644
index 0000000..4c1a6f0
--- /dev/null
+++ b/swift-rs/.gitignore
@@ -0,0 +1,7 @@
+.build/
+target/
+.swiftpm/
+.idea/
+.DS_Store
+icon.txt
+**/Cargo.lock
diff --git a/swift-rs/Cargo.toml b/swift-rs/Cargo.toml
new file mode 100644
index 0000000..2d0172b
--- /dev/null
+++ b/swift-rs/Cargo.toml
@@ -0,0 +1,34 @@
+[package]
+name = "swift-rs"
+version = "1.0.6"
+description = "Call Swift from Rust with ease!"
+authors = ["The swift-rs contributors"]
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/Brendonovich/swift-rs"
+edition = "2021"
+exclude=["/src-swift", "*.swift"]
+build = "src-rs/test-build.rs"
+
+# /bin/sh RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features
+[package.metadata."docs.rs"]
+all-features = true
+rustdoc-args = ["--cfg", "docsrs"]
+
+[lib]
+path = "src-rs/lib.rs"
+
+[dependencies]
+base64 = "0.21.0"
+serde = { version = "1.0", features = ["derive"], optional = true}
+serde_json = { version = "1.0", optional = true }
+
+[build-dependencies]
+serde = { version = "1.0", features = ["derive"]}
+serde_json = { version = "1.0" }
+
+[dev-dependencies]
+serial_test = "0.10"
+
+[features]
+default = []
+build = ["serde", "serde_json"]
diff --git a/swift-rs/LICENSE-APACHE b/swift-rs/LICENSE-APACHE
new file mode 100644
index 0000000..6e7334b
--- /dev/null
+++ b/swift-rs/LICENSE-APACHE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2023 The swift-rs developers
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/swift-rs/LICENSE-MIT b/swift-rs/LICENSE-MIT
new file mode 100644
index 0000000..dd89749
--- /dev/null
+++ b/swift-rs/LICENSE-MIT
@@ -0,0 +1,19 @@
+Copyright (c) 2023 The swift-rs Developers
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/swift-rs/Package.swift b/swift-rs/Package.swift
new file mode 100644
index 0000000..eb1c07d
--- /dev/null
+++ b/swift-rs/Package.swift
@@ -0,0 +1,30 @@
+// swift-tools-version:5.3
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "SwiftRs",
+ platforms: [
+ .macOS(.v10_13),
+ .iOS(.v11),
+ ],
+ products: [
+ // Products define the executables and libraries a package produces, and make them visible to other packages.
+ .library(
+ name: "SwiftRs",
+ targets: ["SwiftRs"]),
+ ],
+ dependencies: [
+ // Dependencies declare other packages that this package depends on.
+ // .package(url: /* package url */, from: "1.0.0"),
+ ],
+ targets: [
+ // Targets are the basic building blocks of a package. A target can define a module or a test suite.
+ // Targets can depend on other targets in this package, and on products in packages this package depends on.
+ .target(
+ name: "SwiftRs",
+ dependencies: [],
+ path: "src-swift")
+ ]
+)
diff --git a/swift-rs/README.md b/swift-rs/README.md
new file mode 100644
index 0000000..2e83a2d
--- /dev/null
+++ b/swift-rs/README.md
@@ -0,0 +1,483 @@
+# swift-rs
+
+
+
+
+Call Swift functions from Rust with ease!
+
+## Setup
+
+Add `swift-rs` to your project's `dependencies` and `build-dependencies`:
+
+```toml
+[dependencies]
+swift-rs = "1.0.5"
+
+[build-dependencies]
+swift-rs = { version = "1.0.5", features = ["build"] }
+```
+
+Next, 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 and make the build type `.static`.
+```swift
+let package = Package(
+ dependencies: [
+ .package(url: "https://github.com/Brendonovich/swift-rs", from: "1.0.5")
+ ],
+ products: [
+ .library(
+ type: .static,
+ ),
+ ],
+ targets: [
+ .target(
+ // Must specify swift-rs as a dependency of your target
+ dependencies: [
+ .product(
+ name: "SwiftRs",
+ package: "swift-rs"
+ )
+ ],
+ )
+ ]
+)
+```
+3. Create a `build.rs` file in your project's root folder, if you don't have one already.
+4. Use `SwiftLinker` in your `build.rs` file to link both the Swift runtime and your Swift package.
+The package name should be the same as is specified in your `Package.swift` file,
+and the path should point to your Swift project's root folder relative to your crate's root folder.
+
+```rust
+use swift_rs::SwiftLinker;
+
+fn build() {
+ // swift-rs has a minimum of macOS 10.13
+ // Ensure the same minimum supported macOS version is specified as in your `Package.swift` file.
+ SwiftLinker::new("10.13")
+ // Only if you are also targetting iOS
+ // Ensure the same minimum supported iOS version is specified as in your `Package.swift` file
+ .with_ios("11")
+ .with_package(PACKAGE_NAME, PACKAGE_PATH)
+ .link();
+
+ // Other build steps
+}
+```
+
+With those steps completed, you should be ready to start using Swift code from Rust!
+
+If you experience the error `dyld[16008]: Library not loaded: @rpath/libswiftCore.dylib`
+when using `swift-rs` with [Tauri](https://tauri.app) ensure you have set your
+[Tauri minimum system version](https://tauri.app/v1/guides/building/macos#setting-a-minimum-system-version)
+to `10.15` or higher in your `tauri.config.json`.
+
+## Calling basic functions
+
+To allow calling a Swift function from Rust, it must follow some rules:
+
+1. It must be global
+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 scalars 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:
+
+```swift
+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 `square_number`.
+
+```swift
+@_cdecl("square_number")
+public func squareNumber(number: Int) -> Int {
+ return number * number
+}
+```
+
+Now that `squareNumber` is properly exposed to Rust, we can start interfacing with it.
+This can be done using the `swift!` macro, with the `Int` type helping to provide a similar function signature:
+
+```rust
+use swift_rs::swift;
+
+swift!(fn square_number(number: Int) -> Int);
+```
+
+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`.
+
+```rust
+fn main() {
+ let input: Int = 4;
+ let output = unsafe { square_number(input) };
+
+ println!("Input: {}, Squared: {}", input, output);
+ // Prints "Input: 4, Squared: 16"
+}
+```
+
+Check [the documentation](TODO) for all available helper types.
+
+## 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:
+
+```swift
+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`:
+
+```swift
+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`:
+
+```swift
+@_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 `SquareNumberResult` in Rust:
+
+```rust
+use swift_rs::{swift, Int, 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: Int,
+ output: Int
+}
+
+// SRObject abstracts away the underlying pointer and will automatically deref to
+// &SquareNumberResult through the Deref trait
+swift!(fn square_number(input: Int) -> SRObject);
+```
+
+Then, using the new return value is just like using `SquareNumberResult` directly:
+
+```rust
+fn main() {
+ let input = 4;
+ let result = unsafe { square_number(input) };
+
+ let result_input = result.input; // 4
+ let result_output = result.output; // 16
+}
+```
+
+Creating objects in Rust and then passing them to Swift is not supported.
+
+## Optionals
+
+`swift-rs` also supports Swift's `nil` type, but only for functions that return optional `NSObject`s.
+Functions returning optional primitives cannot be represented in Objective C, and thus are not supported.
+
+Let's say we have a function returning an optional `SRString`:
+
+```swift
+@_cdecl("optional_string")
+func optionalString(returnNil: Bool) -> SRString? {
+ if (returnNil) return nil
+ else return SRString("lorem ipsum")
+}
+```
+
+Thanks to Rust's [null pointer optimisation](https://doc.rust-lang.org/std/option/index.html#representation),
+the optional nature of `SRString?` can be represented by wrapping `SRString` in Rust's `Option` type!
+
+```rust
+use swift_rs::{swift, Bool, SRString};
+
+swift!(optional_string(return_nil: Bool) -> Option)
+```
+
+Null pointers are actually the reason why a function that returns an optional primitive cannot be represented in C.
+If this were to be supported, how could a `nil` be differentiated from a number? It can't!
+
+## 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**
+
+```swift
+import SwiftRs
+
+@_cdecl("swift_print")
+public func swiftPrint(value: SRString) {
+ // .to_string() converts the SRString to a Swift String
+ print(value.to_string())
+}
+```
+
+```rust
+use swift_rs::{swift, SRString, SwiftRef};
+
+swift!(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**
+
+```swift
+import SwiftRs
+
+@_cdecl("get_string")
+public func getString() -> SRString {
+ let value = "lorem ipsum"
+
+ // SRString can be created from a regular String
+ return SRString(value)
+}
+```
+
+```rust
+use swift_rs::{swift, SRString};
+
+swift!(fn get_string() -> SRString);
+
+fn main() {
+ let value_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;
+
+ // SRString also implements Display
+ println!("{}", value_srstring); // 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.
+
+```swift
+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
+
+ init(_ data: [Int]) {
+ self.data = SRArray(data)
+ }
+}
+
+@_cdecl("get_numbers")
+public func getNumbers() -> IntArray {
+ let numbers = [1, 2, 3, 4]
+
+ return IntArray(numbers)
+}
+```
+
+```rust
+use swift_rs::{Int, SRArray, SRObject};
+
+#[repr(C)]
+struct IntArray {
+ data: SRArray
+}
+
+// Since IntArray extends NSObject in its Swift implementation,
+// it must be wrapped in SRObject on the Rust side
+swift!(fn get_numbers() -> SRObject);
+
+fn main() {
+ let numbers = unsafe { get_numbers() };
+
+ // SRArray can be accessed as a slice via as_slice
+ let numbers_slice: &[Int] = numbers.data.as_slice();
+
+ assert_eq!(numbers_slice, &[1, 2, 3, 4]);
+}
+```
+
+To simplify things on the rust side, 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:
+
+```rust
+// We still need to wrap the array in SRObject since
+// the wrapper class in Swift is an NSObject
+swift!(fn get_numbers() -> SRObject>);
+```
+
+**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:
+
+```swift
+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)
+}
+```
+
+```rust
+use swift_rs::{swift, Int, SRObjectArray};
+
+#[repr(C)]
+struct IntTuple {
+ item1: Int,
+ item2: Int
+}
+
+// No need to wrap IntTuple in SRObject since
+// SRObjectArray does it automatically
+swift!(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 `u8`s - essentially just a byte buffer.
+
+### Tighter Memory Control with `autoreleasepool!`
+
+If you've come to Swift from an Objective-C background, you likely know the utility of `@autoreleasepool` blocks.
+`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!
+
+```rust
+use swift_rs::autoreleasepool;
+
+for _ in 0..10000 {
+ autoreleasepool!({
+ // do some memory intensive thing here
+ });
+}
+```
+
+## Limitations
+
+Currently, the only types that can be created from Rust are number types, boolean, `SRString`, and `SRData`.
+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.
+
+## License
+
+Licensed under either of
+
+ * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
+ * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally
+submitted for inclusion in the work by you, as defined in the Apache-2.0
+license, shall be dual licensed as above, without any additional terms or
+conditions.
diff --git a/swift-rs/example/Cargo.toml b/swift-rs/example/Cargo.toml
new file mode 100644
index 0000000..a2c5888
--- /dev/null
+++ b/swift-rs/example/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "example"
+version = "0.1.0"
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+swift-rs = { path = "../" }
+
+[build-dependencies]
+swift-rs = { path = "../", features = ["build"] }
diff --git a/swift-rs/example/build.rs b/swift-rs/example/build.rs
new file mode 100644
index 0000000..913b415
--- /dev/null
+++ b/swift-rs/example/build.rs
@@ -0,0 +1,9 @@
+use swift_rs::SwiftLinker;
+
+fn main() {
+ // Ensure this matches the versions set in your `Package.swift` file.
+ SwiftLinker::new("10.15")
+ .with_ios("11")
+ .with_package("swift-lib", "./swift-lib/")
+ .link();
+}
diff --git a/swift-rs/example/src/main.rs b/swift-rs/example/src/main.rs
new file mode 100644
index 0000000..00d2c15
--- /dev/null
+++ b/swift-rs/example/src/main.rs
@@ -0,0 +1,39 @@
+use swift_rs::{swift, Bool, Int, SRObject, SRObjectArray, SRString};
+
+#[repr(C)]
+struct Volume {
+ pub name: SRString,
+ path: SRString,
+ total_capacity: Int,
+ available_capacity: Int,
+ is_removable: Bool,
+ is_ejectable: Bool,
+ is_root_filesystem: Bool,
+}
+
+#[repr(C)]
+struct Test {
+ pub null: bool,
+}
+
+swift!(fn get_file_thumbnail_base64(path: &SRString) -> SRString);
+swift!(fn get_mounts() -> SRObjectArray);
+swift!(fn return_nullable(null: Bool) -> Option>);
+
+fn main() {
+ let path = "/Users";
+ let thumbnail = unsafe { get_file_thumbnail_base64(&path.into()) };
+ println!(
+ "length of base64 encoded thumbnail: {}",
+ thumbnail.as_str().len()
+ );
+
+ let mounts = unsafe { get_mounts() };
+ println!("First Volume Name: {}", mounts[0].name);
+
+ let opt = unsafe { return_nullable(true) };
+ println!("function returned nil: {}", opt.is_none());
+
+ let opt = unsafe { return_nullable(false) };
+ println!("function returned data: {}", opt.is_some());
+}
diff --git a/swift-rs/example/swift-lib/.gitignore b/swift-rs/example/swift-lib/.gitignore
new file mode 100644
index 0000000..bb460e7
--- /dev/null
+++ b/swift-rs/example/swift-lib/.gitignore
@@ -0,0 +1,7 @@
+.DS_Store
+/.build
+/Packages
+/*.xcodeproj
+xcuserdata/
+DerivedData/
+.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
diff --git a/swift-rs/example/swift-lib/Package.swift b/swift-rs/example/swift-lib/Package.swift
new file mode 100644
index 0000000..7f59df0
--- /dev/null
+++ b/swift-rs/example/swift-lib/Package.swift
@@ -0,0 +1,30 @@
+// swift-tools-version:5.3
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "swift-lib",
+ platforms: [
+ .macOS(.v10_15), // macOS Catalina. Earliest version that is officially supported by Apple.
+ ],
+ products: [
+ // Products define the executables and libraries a package produces, and make them visible to other packages.
+ .library(
+ name: "swift-lib",
+ type: .static,
+ targets: ["swift-lib"]),
+ ],
+ dependencies: [
+ // Dependencies declare other packages that this package depends on.
+ .package(name: "SwiftRs", path: "../../")
+ ],
+ targets: [
+ // Targets are the basic building blocks of a package. A target can define a module or a test suite.
+ // Targets can depend on other targets in this package, and on products in packages this package depends on.
+ .target(
+ name: "swift-lib",
+ dependencies: [.product(name: "SwiftRs", package: "SwiftRs")],
+ path: "src")
+ ]
+)
diff --git a/swift-rs/example/swift-lib/README.md b/swift-rs/example/swift-lib/README.md
new file mode 100644
index 0000000..c88b3b8
--- /dev/null
+++ b/swift-rs/example/swift-lib/README.md
@@ -0,0 +1,3 @@
+# swift
+
+A description of this package.
diff --git a/swift-rs/example/swift-lib/src/lib.swift b/swift-rs/example/swift-lib/src/lib.swift
new file mode 100644
index 0000000..df841a8
--- /dev/null
+++ b/swift-rs/example/swift-lib/src/lib.swift
@@ -0,0 +1,94 @@
+import SwiftRs
+import AppKit
+
+@_cdecl("get_file_thumbnail_base64")
+func getFileThumbnailBase64(path: SRString) -> SRString {
+ let path = path.toString();
+
+ let image = NSWorkspace.shared.icon(forFile: path)
+ let bitmap = NSBitmapImageRep(data: image.tiffRepresentation!)!.representation(using: .png, properties: [:])!
+
+ return SRString(bitmap.base64EncodedString())
+}
+
+class Volume: NSObject {
+ var name: SRString
+ var path: SRString
+ var total_capacity: Int
+ var available_capacity: Int
+ var is_removable: Bool
+ var is_ejectable: Bool
+ var is_root_filesystem: Bool
+
+ public init(name: String, path: String, total_capacity: Int, available_capacity: Int, is_removable: Bool, is_ejectable: Bool, is_root_filesystem: Bool) {
+ self.name = SRString(name);
+ self.path = SRString(path);
+ self.total_capacity = total_capacity
+ self.available_capacity = available_capacity
+ self.is_removable = is_removable
+ self.is_ejectable = is_ejectable
+ self.is_root_filesystem = is_root_filesystem
+ }
+}
+
+@_cdecl("get_mounts")
+func getMounts() -> SRObjectArray {
+ let keys: [URLResourceKey] = [
+ .volumeNameKey,
+ .volumeIsRemovableKey,
+ .volumeIsEjectableKey,
+ .volumeTotalCapacityKey,
+ .volumeAvailableCapacityKey,
+ .volumeIsRootFileSystemKey,
+ ]
+
+ let paths = autoreleasepool {
+ FileManager().mountedVolumeURLs(includingResourceValuesForKeys: keys, options: [])
+ }
+
+ var validMounts: [Volume] = []
+
+ if let urls = paths {
+ autoreleasepool {
+ for url in urls {
+ let components = url.pathComponents
+ if components.count == 1 || components.count > 1
+ && components[1] == "Volumes"
+ {
+ let metadata = try? url.promisedItemResourceValues(forKeys: Set(keys))
+
+ let volume = Volume(
+ name: metadata?.volumeName ?? "",
+ path: url.path,
+ total_capacity: metadata?.volumeTotalCapacity ?? 0,
+ available_capacity: metadata?.volumeAvailableCapacity ?? 0,
+ is_removable: metadata?.volumeIsRemovable ?? false,
+ is_ejectable: metadata?.volumeIsEjectable ?? false,
+ is_root_filesystem: metadata?.volumeIsRootFileSystem ?? false
+ )
+
+
+ validMounts.append(volume)
+ }
+ }
+ }
+ }
+
+ return SRObjectArray(validMounts)
+}
+
+class Test: NSObject {
+ var null: Bool
+
+ public init(_ null: Bool)
+ {
+ self.null = null;
+ }
+}
+
+@_cdecl("return_nullable")
+func returnNullable(null: Bool) -> Test? {
+ if (null == true) { return nil }
+
+ return Test(null)
+}
diff --git a/swift-rs/src-rs/autorelease.rs b/swift-rs/src-rs/autorelease.rs
new file mode 100644
index 0000000..bb1007d
--- /dev/null
+++ b/swift-rs/src-rs/autorelease.rs
@@ -0,0 +1,26 @@
+/// Run code with its own autorelease pool. Semantically, this is identical
+/// to [`@autoreleasepool`](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html)
+/// in Objective-C
+///
+///
+/// ```no_run
+/// use swift_rs::autoreleasepool;
+///
+/// autoreleasepool!({
+/// // do something memory intensive stuff
+/// })
+/// ```
+#[macro_export]
+macro_rules! autoreleasepool {
+ ( $expr:expr ) => {{
+ extern "C" {
+ fn objc_autoreleasePoolPush() -> *mut std::ffi::c_void;
+ fn objc_autoreleasePoolPop(context: *mut std::ffi::c_void);
+ }
+
+ let pool = unsafe { objc_autoreleasePoolPush() };
+ let r = { $expr };
+ unsafe { objc_autoreleasePoolPop(pool) };
+ r
+ }};
+}
diff --git a/swift-rs/src-rs/build.rs b/swift-rs/src-rs/build.rs
new file mode 100644
index 0000000..394b9eb
--- /dev/null
+++ b/swift-rs/src-rs/build.rs
@@ -0,0 +1,326 @@
+#![allow(dead_code)]
+use std::{env, fmt::Display, path::Path, path::PathBuf, process::Command};
+
+use serde::Deserialize;
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct SwiftTarget {
+ triple: String,
+ unversioned_triple: String,
+ module_triple: String,
+ //pub swift_runtime_compatibility_version: String,
+ #[serde(rename = "librariesRequireRPath")]
+ libraries_require_rpath: bool,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase")]
+struct SwiftPaths {
+ runtime_library_paths: Vec,
+ runtime_library_import_paths: Vec,
+ runtime_resource_path: String,
+}
+
+#[derive(Deserialize)]
+struct SwiftEnv {
+ target: SwiftTarget,
+ paths: SwiftPaths,
+}
+
+impl SwiftEnv {
+ fn new(minimum_macos_version: &str, minimum_ios_version: Option<&str>) -> Self {
+ let rust_target = RustTarget::from_env();
+ let target = rust_target.swift_target_triple(minimum_macos_version, minimum_ios_version);
+
+ let swift_target_info_str = Command::new("swift")
+ .args(["-target", &target, "-print-target-info"])
+ .output()
+ .unwrap()
+ .stdout;
+
+ serde_json::from_slice(&swift_target_info_str).unwrap()
+ }
+}
+
+#[allow(clippy::upper_case_acronyms)]
+enum RustTargetOS {
+ MacOS,
+ IOS,
+}
+
+impl RustTargetOS {
+ fn from_env() -> Self {
+ match env::var("CARGO_CFG_TARGET_OS").unwrap().as_str() {
+ "macos" => RustTargetOS::MacOS,
+ "ios" => RustTargetOS::IOS,
+ _ => panic!("unexpected target operating system"),
+ }
+ }
+
+ fn to_swift(&self) -> &'static str {
+ match self {
+ Self::MacOS => "macosx",
+ Self::IOS => "ios",
+ }
+ }
+}
+
+impl Display for RustTargetOS {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::MacOS => write!(f, "macos"),
+ Self::IOS => write!(f, "ios"),
+ }
+ }
+}
+
+#[allow(clippy::upper_case_acronyms)]
+enum SwiftSDK {
+ MacOS,
+ IOS,
+ IOSSimulator,
+}
+
+impl SwiftSDK {
+ fn from_os(os: &RustTargetOS) -> Self {
+ let target = env::var("TARGET").unwrap();
+ let simulator = target.ends_with("ios-sim")
+ || (target.starts_with("x86_64") && target.ends_with("ios"));
+
+ match os {
+ RustTargetOS::MacOS => Self::MacOS,
+ RustTargetOS::IOS if simulator => Self::IOSSimulator,
+ RustTargetOS::IOS => Self::IOS,
+ }
+ }
+
+ fn clang_lib_extension(&self) -> &'static str {
+ match self {
+ Self::MacOS => "osx",
+ Self::IOS => "ios",
+ Self::IOSSimulator => "iossim",
+ }
+ }
+}
+
+impl Display for SwiftSDK {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::MacOS => write!(f, "macosx"),
+ Self::IOSSimulator => write!(f, "iphonesimulator"),
+ Self::IOS => write!(f, "iphoneos"),
+ }
+ }
+}
+
+struct RustTarget {
+ arch: String,
+ os: RustTargetOS,
+ sdk: SwiftSDK,
+}
+
+impl RustTarget {
+ fn from_env() -> Self {
+ let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
+ let os = RustTargetOS::from_env();
+ let sdk = SwiftSDK::from_os(&os);
+
+ Self { arch, os, sdk }
+ }
+
+ fn swift_target_triple(
+ &self,
+ minimum_macos_version: &str,
+ minimum_ios_version: Option<&str>,
+ ) -> String {
+ let unversioned = self.unversioned_swift_target_triple();
+ format!(
+ "{unversioned}{}{}",
+ match (&self.os, minimum_ios_version) {
+ (RustTargetOS::MacOS, _) => minimum_macos_version,
+ (RustTargetOS::IOS, Some(version)) => version,
+ _ => "",
+ },
+ // simulator suffix
+ matches!(self.sdk, SwiftSDK::IOSSimulator)
+ .then(|| "-simulator".to_string())
+ .unwrap_or_default()
+ )
+ }
+
+ fn unversioned_swift_target_triple(&self) -> String {
+ format!(
+ "{}-apple-{}",
+ match self.arch.as_str() {
+ "aarch64" => "arm64",
+ a => a,
+ },
+ self.os.to_swift(),
+ )
+ }
+}
+
+struct SwiftPackage {
+ name: String,
+ path: PathBuf,
+}
+
+/// Builder for linking the Swift runtime and custom packages.
+#[cfg(feature = "build")]
+pub struct SwiftLinker {
+ packages: Vec,
+ macos_min_version: String,
+ ios_min_version: Option,
+}
+
+impl SwiftLinker {
+ /// Creates a new [`SwiftLinker`] with a minimum macOS verison.
+ ///
+ /// Minimum macOS version must be at least 10.13.
+ pub fn new(macos_min_version: &str) -> Self {
+ Self {
+ packages: vec![],
+ macos_min_version: macos_min_version.to_string(),
+ ios_min_version: None,
+ }
+ }
+
+ /// Instructs the [`SwiftLinker`] to also compile for iOS
+ /// using the specified minimum iOS version.
+ ///
+ /// Minimum iOS version must be at least 11.
+ pub fn with_ios(mut self, min_version: &str) -> Self {
+ self.ios_min_version = Some(min_version.to_string());
+ self
+ }
+
+ /// Adds a package to be linked against.
+ /// `name` should match the `name` field in your `Package.swift`,
+ /// and `path` should point to the root of your Swift package relative
+ /// to your crate's root.
+ pub fn with_package(mut self, name: &str, path: impl AsRef) -> Self {
+ self.packages.extend([SwiftPackage {
+ name: name.to_string(),
+ path: path.as_ref().into(),
+ }]);
+
+ self
+ }
+
+ /// Links the Swift runtime, then builds and links the provided packages.
+ /// This does not (yet) automatically rebuild your Swift files when they are modified,
+ /// you'll need to modify/save your `build.rs` file for that.
+ pub fn link(self) {
+ let swift_env = SwiftEnv::new(&self.macos_min_version, self.ios_min_version.as_deref());
+
+ #[allow(clippy::uninlined_format_args)]
+ for path in swift_env.paths.runtime_library_paths {
+ println!("cargo:rustc-link-search=native={path}");
+ }
+
+ let debug = env::var("DEBUG").unwrap() == "true";
+ let configuration = if debug { "debug" } else { "release" };
+ let rust_target = RustTarget::from_env();
+
+ link_clang_rt(&rust_target);
+
+ for package in self.packages {
+ let package_path =
+ Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join(&package.path);
+ let out_path = Path::new(&env::var("OUT_DIR").unwrap())
+ .join("swift-rs")
+ .join(&package.name);
+
+ let sdk_path_output = Command::new("xcrun")
+ .args(["--sdk", &rust_target.sdk.to_string(), "--show-sdk-path"])
+ .output()
+ .unwrap();
+ if !sdk_path_output.status.success() {
+ panic!(
+ "Failed to get SDK path with `xcrun --sdk {} --show-sdk-path`",
+ rust_target.sdk
+ );
+ }
+
+ let sdk_path = String::from_utf8_lossy(&sdk_path_output.stdout);
+
+ let mut command = Command::new("swift");
+ command.current_dir(&package.path);
+
+ let arch = match std::env::consts::ARCH {
+ "aarch64" => "arm64",
+ arch => arch,
+ };
+
+ command
+ // Build the package (duh)
+ .args(["build"])
+ // SDK path for regular compilation (idk)
+ .args(["--sdk", sdk_path.trim()])
+ // Release/Debug configuration
+ .args(["-c", configuration])
+ .args(["--arch", arch])
+ // Where the artifacts will be generated to
+ .args(["--build-path", &out_path.display().to_string()])
+ // Override SDK path for each swiftc instance.
+ // Necessary for iOS compilation.
+ .args(["-Xswiftc", "-sdk"])
+ .args(["-Xswiftc", sdk_path.trim()])
+ // Override target triple for each swiftc instance.
+ // Necessary for iOS compilation.
+ .args(["-Xswiftc", "-target"])
+ .args([
+ "-Xswiftc",
+ &rust_target.swift_target_triple(
+ &self.macos_min_version,
+ self.ios_min_version.as_deref(),
+ ),
+ ]);
+
+ if !command.status().unwrap().success() {
+ panic!("Failed to compile swift package {}", package.name);
+ }
+
+ let search_path = out_path
+ // swift build uses this output folder no matter what is the target
+ .join(format!(
+ "{}-apple-macosx",
+ arch
+ ))
+ .join(configuration);
+
+ println!("cargo:rerun-if-changed={}", package_path.display());
+ println!("cargo:rustc-link-search=native={}", search_path.display());
+ println!("cargo:rustc-link-lib=static={}", package.name);
+ }
+ }
+}
+
+fn link_clang_rt(rust_target: &RustTarget) {
+ println!(
+ "cargo:rustc-link-lib=clang_rt.{}",
+ rust_target.sdk.clang_lib_extension()
+ );
+ println!("cargo:rustc-link-search={}", clang_link_search_path());
+}
+
+fn clang_link_search_path() -> String {
+ let output = std::process::Command::new(
+ std::env::var("SWIFT_RS_CLANG").unwrap_or_else(|_| "/usr/bin/clang".to_string()),
+ )
+ .arg("--print-search-dirs")
+ .output()
+ .unwrap();
+ if !output.status.success() {
+ panic!("Can't get search paths from clang");
+ }
+ let stdout = String::from_utf8_lossy(&output.stdout);
+ for line in stdout.lines() {
+ if line.contains("libraries: =") {
+ let path = line.split('=').nth(1).unwrap();
+ return format!("{}/lib/darwin", path);
+ }
+ }
+ panic!("clang is missing search paths");
+}
diff --git a/swift-rs/src-rs/dark_magic.rs b/swift-rs/src-rs/dark_magic.rs
new file mode 100644
index 0000000..7cb8c36
--- /dev/null
+++ b/swift-rs/src-rs/dark_magic.rs
@@ -0,0 +1,90 @@
+/// This retain-balancing algorithm is cool but likely isn't required.
+/// I'm keeping it around in case it's necessary one day.
+
+// #[derive(Clone, Copy, Debug)]
+// enum ValueArity {
+// Reference,
+// Value,
+// }
+
+// pub unsafe fn balance_ptrs(args: Vec<(*const c_void, bool)>, ret: Vec<(*const c_void, bool)>) {
+// fn collect_references(
+// v: Vec<(*const c_void, bool)>,
+// ) -> BTreeMap<*const c_void, Vec> {
+// v.into_iter().fold(
+// BTreeMap::<_, Vec>::new(),
+// |mut map, (ptr, is_ref)| {
+// map.entry(ptr).or_default().push(if is_ref {
+// ValueArity::Reference
+// } else {
+// ValueArity::Value
+// });
+// map
+// },
+// )
+// }
+
+// let mut args = collect_references(args);
+// let mut ret = collect_references(ret);
+
+// let both_counts = args
+// .clone()
+// .into_iter()
+// .flat_map(|(arg, values)| {
+// ret.remove(&arg).map(|ret| {
+// args.remove(&arg);
+
+// let ret_values = ret
+// .iter()
+// .filter(|v| matches!(v, ValueArity::Value))
+// .count() as isize;
+
+// let arg_references = values
+// .iter()
+// .filter(|v| matches!(v, ValueArity::Reference))
+// .count() as isize;
+
+// let ref_in_value_out_retains = min(ret_values, arg_references);
+
+// (arg, ref_in_value_out_retains)
+// })
+// })
+// .collect::>();
+
+// let arg_counts = args.into_iter().map(|(ptr, values)| {
+// let count = values
+// .into_iter()
+// .filter(|v| matches!(v, ValueArity::Value))
+// .count() as isize;
+// (ptr, count)
+// });
+
+// let ret_counts = ret
+// .into_iter()
+// .map(|(ptr, values)| {
+// let count = values
+// .into_iter()
+// .filter(|v| matches!(v, ValueArity::Value))
+// .count() as isize;
+// (ptr, count)
+// })
+// .collect::>();
+
+// both_counts
+// .into_iter()
+// .chain(arg_counts)
+// .chain(ret_counts)
+// .for_each(|(ptr, count)| match count {
+// 0 => {}
+// n if n > 0 => {
+// for _ in 0..n {
+// retain_object(ptr)
+// }
+// }
+// n => {
+// for _ in n..0 {
+// release_object(ptr)
+// }
+// }
+// });
+// }
diff --git a/swift-rs/src-rs/lib.rs b/swift-rs/src-rs/lib.rs
new file mode 100644
index 0000000..3933189
--- /dev/null
+++ b/swift-rs/src-rs/lib.rs
@@ -0,0 +1,20 @@
+//! Call Swift functions from Rust with ease!
+#![cfg_attr(docsrs, feature(doc_cfg))]
+
+mod autorelease;
+mod swift;
+mod swift_arg;
+mod swift_ret;
+mod types;
+
+pub use autorelease::*;
+pub use swift::*;
+pub use swift_arg::*;
+pub use swift_ret::*;
+pub use types::*;
+
+#[cfg(feature = "build")]
+#[cfg_attr(docsrs, doc(cfg(feature = "build")))]
+mod build;
+#[cfg(feature = "build")]
+pub use build::*;
diff --git a/swift-rs/src-rs/swift.rs b/swift-rs/src-rs/swift.rs
new file mode 100644
index 0000000..0b4f370
--- /dev/null
+++ b/swift-rs/src-rs/swift.rs
@@ -0,0 +1,101 @@
+use std::ffi::c_void;
+
+use crate::*;
+
+/// Reference to an `NSObject` for internal use by [`swift!`].
+#[must_use = "A Ref MUST be sent over to the Swift side"]
+#[repr(transparent)]
+pub struct SwiftRef<'a, T: SwiftObject>(&'a SRObjectImpl);
+
+impl<'a, T: SwiftObject> SwiftRef<'a, T> {
+ pub(crate) unsafe fn retain(&self) {
+ retain_object(self.0 as *const _ as *const c_void)
+ }
+}
+
+/// A type that is represented as an `NSObject` in Swift.
+pub trait SwiftObject {
+ type Shape;
+
+ /// Gets a reference to the `SRObject` at the root of a `SwiftObject`
+ fn get_object(&self) -> &SRObject;
+
+ /// Creates a [`SwiftRef`] for an object which can be used when calling a Swift function.
+ /// This function should never be called manually,
+ /// instead you should rely on the [`swift!`] macro to call it for you.
+ ///
+ /// # Safety
+ /// This function converts the [`NonNull`](std::ptr::NonNull)
+ /// inside an [`SRObject`] into a reference,
+ /// implicitly assuming that the pointer is still valid.
+ /// The inner pointer is private,
+ /// and the returned [`SwiftRef`] is bound to the lifetime of the original [`SRObject`],
+ /// so if you use `swift-rs` as normal this function should be safe.
+ unsafe fn swift_ref(&self) -> SwiftRef
+ where
+ Self: Sized,
+ {
+ SwiftRef(self.get_object().0.as_ref())
+ }
+
+ /// Adds a retain to an object.
+ ///
+ /// # Safety
+ /// Just don't call this, let [`swift!`] handle it for you.
+ unsafe fn retain(&self)
+ where
+ Self: Sized,
+ {
+ self.swift_ref().retain()
+ }
+}
+
+swift!(pub(crate) fn retain_object(obj: *const c_void));
+swift!(pub(crate) fn release_object(obj: *const c_void));
+swift!(pub(crate) fn data_from_bytes(data: *const u8, size: Int) -> SRData);
+swift!(pub(crate) fn string_from_bytes(data: *const u8, size: Int) -> SRString);
+
+/// Declares a function defined in a swift library.
+/// As long as this macro is used, retain counts of arguments
+/// and return values will be correct.
+///
+/// Use this macro as if the contents were going directly
+/// into an `extern "C"` block.
+///
+/// ```
+/// use swift_rs::*;
+///
+/// swift!(fn echo(string: &SRString) -> SRString);
+///
+/// let string: SRString = "test".into();
+/// let result = unsafe { echo(&string) };
+///
+/// assert_eq!(result.as_str(), string.as_str())
+/// ```
+///
+/// # Details
+///
+/// Internally this macro creates a wrapping function around an `extern "C"` block
+/// that represents the actual Swift function. This is done in order to restrict the types
+/// that can be used as arguments and return types, and to ensure that retain counts of returned
+/// values are appropriately balanced.
+#[macro_export]
+macro_rules! swift {
+ ($vis:vis fn $name:ident $(<$($lt:lifetime),+>)? ($($arg:ident: $arg_ty:ty),*) $(-> $ret:ty)?) => {
+ $vis unsafe fn $name $(<$($lt),*>)? ($($arg: $arg_ty),*) $(-> $ret)? {
+ extern "C" {
+ fn $name $(<$($lt),*>)? ($($arg: <$arg_ty as $crate::SwiftArg>::ArgType),*) $(-> $ret)?;
+ }
+
+ let res = {
+ $(let $arg = $crate::SwiftArg::as_arg(&$arg);)*
+
+ $name($($arg),*)
+ };
+
+ $crate::SwiftRet::retain(&res);
+
+ res
+ }
+ };
+}
diff --git a/swift-rs/src-rs/swift_arg.rs b/swift-rs/src-rs/swift_arg.rs
new file mode 100644
index 0000000..689650e
--- /dev/null
+++ b/swift-rs/src-rs/swift_arg.rs
@@ -0,0 +1,75 @@
+use std::ffi::c_void;
+
+use crate::{swift::SwiftObject, *};
+
+/// Identifies a type as being a valid argument in a Swift function.
+pub trait SwiftArg<'a> {
+ type ArgType;
+
+ /// Creates a swift-compatible version of the argument.
+ /// For primitives this just returns `self`,
+ /// but for [`SwiftObject`] types it wraps them in [`SwiftRef`].
+ ///
+ /// This function is called within the [`swift!`] macro.
+ ///
+ /// # Safety
+ ///
+ /// Creating a [`SwiftRef`] is inherently unsafe,
+ /// but is reliable if using the [`swift!`] macro,
+ /// so it is not advised to call this function manually.
+ unsafe fn as_arg(&'a self) -> Self::ArgType;
+}
+
+macro_rules! primitive_impl {
+ ($($t:ty),+) => {
+ $(impl<'a> SwiftArg<'a> for $t {
+ type ArgType = $t;
+
+ unsafe fn as_arg(&'a self) -> Self::ArgType {
+ *self
+ }
+ })+
+ };
+}
+
+primitive_impl!(
+ Bool,
+ Int,
+ Int8,
+ Int16,
+ Int32,
+ Int64,
+ UInt,
+ UInt8,
+ UInt16,
+ UInt32,
+ UInt64,
+ Float32,
+ Float64,
+ *const c_void,
+ *mut c_void,
+ *const u8,
+ ()
+);
+
+macro_rules! ref_impl {
+ ($($t:ident $(<$($gen:ident),+>)?),+) => {
+ $(impl<'a $($(, $gen: 'a),+)?> SwiftArg<'a> for $t$(<$($gen),+>)? {
+ type ArgType = SwiftRef<'a, $t$(<$($gen),+>)?>;
+
+ unsafe fn as_arg(&'a self) -> Self::ArgType {
+ self.swift_ref()
+ }
+ })+
+ };
+}
+
+ref_impl!(SRObject, SRArray, SRData, SRString);
+
+impl<'a, T: SwiftArg<'a>> SwiftArg<'a> for &T {
+ type ArgType = T::ArgType;
+
+ unsafe fn as_arg(&'a self) -> Self::ArgType {
+ (*self).as_arg()
+ }
+}
diff --git a/swift-rs/src-rs/swift_ret.rs b/swift-rs/src-rs/swift_ret.rs
new file mode 100644
index 0000000..d853d12
--- /dev/null
+++ b/swift-rs/src-rs/swift_ret.rs
@@ -0,0 +1,55 @@
+use crate::{swift::SwiftObject, *};
+use std::ffi::c_void;
+
+/// Identifies a type as being a valid return type from a Swift function.
+/// For types that are objects which need extra retains,
+/// the [`retain`](SwiftRet::retain) function will be re-implemented.
+pub trait SwiftRet {
+ /// Adds a retain to the value if possible
+ ///
+ /// # Safety
+ /// Just don't use this.
+ /// Let [`swift!`] handle it.
+ unsafe fn retain(&self) {}
+}
+
+macro_rules! primitive_impl {
+ ($($t:ty),+) => {
+ $(impl SwiftRet for $t {
+ })+
+ };
+}
+
+primitive_impl!(
+ Bool,
+ Int,
+ Int8,
+ Int16,
+ Int32,
+ Int64,
+ UInt,
+ UInt8,
+ UInt16,
+ UInt32,
+ UInt64,
+ Float32,
+ Float64,
+ *const c_void,
+ *mut c_void,
+ *const u8,
+ ()
+);
+
+impl SwiftRet for Option {
+ unsafe fn retain(&self) {
+ if let Some(v) = self {
+ v.retain()
+ }
+ }
+}
+
+impl SwiftRet for T {
+ unsafe fn retain(&self) {
+ (*self).retain()
+ }
+}
diff --git a/swift-rs/src-rs/test-build.rs b/swift-rs/src-rs/test-build.rs
new file mode 100644
index 0000000..da43c63
--- /dev/null
+++ b/swift-rs/src-rs/test-build.rs
@@ -0,0 +1,20 @@
+//! Build script for swift-rs that is a no-op for normal builds, but can be enabled
+//! to include test swift library based on env var `TEST_SWIFT_RS=true` with the
+//! `build` feature being enabled.
+
+#[cfg(feature = "build")]
+mod build;
+
+fn main() {
+ println!("cargo:rerun-if-env-changed=TEST_SWIFT_RS");
+
+ #[cfg(feature = "build")]
+ if std::env::var("TEST_SWIFT_RS").unwrap_or_else(|_| "false".into()) == "true" {
+ use build::SwiftLinker;
+
+ SwiftLinker::new("10.15")
+ .with_ios("11")
+ .with_package("test-swift", "tests/swift-pkg")
+ .link();
+ }
+}
diff --git a/swift-rs/src-rs/types/array.rs b/swift-rs/src-rs/types/array.rs
new file mode 100644
index 0000000..fc69069
--- /dev/null
+++ b/swift-rs/src-rs/types/array.rs
@@ -0,0 +1,110 @@
+use std::{ops::Deref, ptr::NonNull};
+
+use crate::swift::SwiftObject;
+
+use super::SRObject;
+
+/// Wrapper of [`SRArray`] exclusively for arrays of objects.
+/// Equivalent to `SRObjectArray` in Swift.
+// SRArray is wrapped in SRObject since the Swift implementation extends NSObject
+pub type SRObjectArray = SRObject>>;
+
+#[doc(hidden)]
+#[repr(C)]
+pub struct SRArrayImpl {
+ data: NonNull,
+ length: usize,
+}
+
+/// General array type for objects and scalars.
+///
+/// ## Returning Directly
+///
+/// When returning an `SRArray` from a Swift function,
+/// you will need to wrap it in an `NSObject` class since
+/// Swift doesn't permit returning generic types from `@_cdecl` functions.
+/// To account for the wrapping `NSObject`, the array must be wrapped
+/// in `SRObject` on the Rust side.
+///
+/// ```rust
+/// use swift_rs::{swift, SRArray, SRObject, Int};
+///
+/// swift!(fn get_int_array() -> SRObject>);
+///
+/// let array = unsafe { get_int_array() };
+///
+/// assert_eq!(array.as_slice(), &[1, 2, 3])
+/// ```
+/// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L19)
+///
+/// ## Returning in a Struct fIeld
+///
+/// When returning an `SRArray` from a custom struct that is itself an `NSObject`,
+/// the above work is already done for you.
+/// Assuming your custom struct is already wrapped in `SRObject` in Rust,
+/// `SRArray` will work normally.
+///
+/// ```rust
+/// use swift_rs::{swift, SRArray, SRObject, Int};
+///
+/// #[repr(C)]
+/// struct ArrayStruct {
+/// array: SRArray
+/// }
+///
+/// swift!(fn get_array_struct() -> SRObject);
+///
+/// let data = unsafe { get_array_struct() };
+///
+/// assert_eq!(data.array.as_slice(), &[4, 5, 6]);
+/// ```
+/// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L32)
+#[repr(transparent)]
+pub struct SRArray(SRObject>);
+
+impl SRArray {
+ pub fn as_slice(&self) -> &[T] {
+ self.0.as_slice()
+ }
+}
+
+impl SwiftObject for SRArray {
+ type Shape = SRArrayImpl;
+
+ fn get_object(&self) -> &SRObject {
+ &self.0
+ }
+}
+
+impl Deref for SRArray {
+ type Target = [T];
+
+ fn deref(&self) -> &Self::Target {
+ self.0.as_slice()
+ }
+}
+
+impl SRArrayImpl {
+ pub fn as_slice(&self) -> &[T] {
+ unsafe { std::slice::from_raw_parts(self.data.as_ref(), self.length) }
+ }
+}
+
+#[cfg(feature = "serde")]
+impl serde::Serialize for SRArray
+where
+ T: serde::Serialize,
+{
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: serde::Serializer,
+ {
+ use serde::ser::SerializeSeq;
+
+ let mut seq = serializer.serialize_seq(Some(self.len()))?;
+ for item in self.iter() {
+ seq.serialize_element(item)?;
+ }
+ seq.end()
+ }
+}
diff --git a/swift-rs/src-rs/types/data.rs b/swift-rs/src-rs/types/data.rs
new file mode 100644
index 0000000..e982235
--- /dev/null
+++ b/swift-rs/src-rs/types/data.rs
@@ -0,0 +1,75 @@
+use crate::{
+ swift::{self, SwiftObject},
+ Int,
+};
+
+use super::{array::SRArray, SRObject};
+
+use std::ops::Deref;
+
+type Data = SRArray;
+
+/// Convenience type for working with byte buffers,
+/// analagous to `SRData` in Swift.
+///
+/// ```rust
+/// use swift_rs::{swift, SRData};
+///
+/// swift!(fn get_data() -> SRData);
+///
+/// let data = unsafe { get_data() };
+///
+/// assert_eq!(data.as_ref(), &[1, 2, 3])
+/// ```
+/// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L68)
+#[repr(transparent)]
+pub struct SRData(SRObject);
+
+impl SRData {
+ ///
+ pub fn as_slice(&self) -> &[u8] {
+ self
+ }
+
+ pub fn to_vec(&self) -> Vec {
+ self.as_slice().to_vec()
+ }
+}
+
+impl SwiftObject for SRData {
+ type Shape = Data;
+
+ fn get_object(&self) -> &SRObject {
+ &self.0
+ }
+}
+
+impl Deref for SRData {
+ type Target = [u8];
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl AsRef<[u8]> for SRData {
+ fn as_ref(&self) -> &[u8] {
+ self
+ }
+}
+
+impl From<&[u8]> for SRData {
+ fn from(value: &[u8]) -> Self {
+ unsafe { swift::data_from_bytes(value.as_ptr(), value.len() as Int) }
+ }
+}
+
+#[cfg(feature = "serde")]
+impl serde::Serialize for SRData {
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: serde::Serializer,
+ {
+ serializer.serialize_bytes(self)
+ }
+}
diff --git a/swift-rs/src-rs/types/mod.rs b/swift-rs/src-rs/types/mod.rs
new file mode 100644
index 0000000..90d9465
--- /dev/null
+++ b/swift-rs/src-rs/types/mod.rs
@@ -0,0 +1,11 @@
+mod array;
+mod data;
+mod object;
+mod scalars;
+mod string;
+
+pub use array::*;
+pub use data::*;
+pub use object::*;
+pub use scalars::*;
+pub use string::*;
diff --git a/swift-rs/src-rs/types/object.rs b/swift-rs/src-rs/types/object.rs
new file mode 100644
index 0000000..49748a7
--- /dev/null
+++ b/swift-rs/src-rs/types/object.rs
@@ -0,0 +1,75 @@
+use crate::swift::{self, SwiftObject};
+use std::{ffi::c_void, ops::Deref, ptr::NonNull};
+
+#[doc(hidden)]
+#[repr(C)]
+pub struct SRObjectImpl {
+ _nsobject_offset: u8,
+ data: T,
+}
+
+/// Wrapper for arbitrary `NSObject` types.
+///
+/// When returning an `NSObject`, its Rust type must be wrapped in `SRObject`.
+/// The type must also be annotated with `#[repr(C)]` to ensure its memory layout
+/// is identical to its Swift counterpart's.
+///
+/// ```rust
+/// use swift_rs::{swift, SRObject, Int, Bool};
+///
+/// #[repr(C)]
+/// struct CustomObject {
+/// a: Int,
+/// b: Bool
+/// }
+///
+/// swift!(fn get_custom_object() -> SRObject);
+///
+/// let value = unsafe { get_custom_object() };
+///
+/// let reference: &CustomObject = value.as_ref();
+/// ```
+/// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L49)
+#[repr(transparent)]
+pub struct SRObject(pub(crate) NonNull>);
+
+impl SwiftObject for SRObject {
+ type Shape = T;
+
+ fn get_object(&self) -> &SRObject {
+ self
+ }
+}
+
+impl Deref for SRObject {
+ type Target = T;
+
+ fn deref(&self) -> &T {
+ unsafe { &self.0.as_ref().data }
+ }
+}
+
+impl AsRef for SRObject {
+ fn as_ref(&self) -> &T {
+ self
+ }
+}
+
+impl Drop for SRObject {
+ fn drop(&mut self) {
+ unsafe { swift::release_object(self.0.as_ref() as *const _ as *const c_void) }
+ }
+}
+
+#[cfg(feature = "serde")]
+impl serde::Serialize for SRObject
+where
+ T: serde::Serialize,
+{
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: serde::Serializer,
+ {
+ self.deref().serialize(serializer)
+ }
+}
diff --git a/swift-rs/src-rs/types/scalars.rs b/swift-rs/src-rs/types/scalars.rs
new file mode 100644
index 0000000..226f3bd
--- /dev/null
+++ b/swift-rs/src-rs/types/scalars.rs
@@ -0,0 +1,34 @@
+/// Swift's [`Bool`](https://developer.apple.com/documentation/swift/bool) type
+pub type Bool = bool;
+
+/// Swift's [`Int`](https://developer.apple.com/documentation/swift/int) type
+pub type Int = isize;
+/// Swift's [`Int8`](https://developer.apple.com/documentation/swift/int8) type
+pub type Int8 = i8;
+/// Swift's [`Int16`](https://developer.apple.com/documentation/swift/int16) type
+pub type Int16 = i16;
+/// Swift's [`Int32`](https://developer.apple.com/documentation/swift/int32) type
+pub type Int32 = i32;
+/// Swift's [`Int64`](https://developer.apple.com/documentation/swift/int64) type
+pub type Int64 = i64;
+
+/// Swift's [`UInt`](https://developer.apple.com/documentation/swift/uint) type
+pub type UInt = usize;
+/// Swift's [`UInt8`](https://developer.apple.com/documentation/swift/uint8) type
+pub type UInt8 = u8;
+/// Swift's [`UInt16`](https://developer.apple.com/documentation/swift/uint16) type
+pub type UInt16 = u16;
+/// Swift's [`UInt32`](https://developer.apple.com/documentation/swift/uint32) type
+pub type UInt32 = u32;
+/// Swift's [`UInt64`](https://developer.apple.com/documentation/swift/uint64) type
+pub type UInt64 = u64;
+
+/// Swift's [`Float`](https://developer.apple.com/documentation/swift/float) type
+pub type Float = f32;
+/// Swift's [`Double`](https://developer.apple.com/documentation/swift/double) type
+pub type Double = f64;
+
+/// Swift's [`Float32`](https://developer.apple.com/documentation/swift/float32) type
+pub type Float32 = f32;
+/// Swift's [`Float64`](https://developer.apple.com/documentation/swift/float64) type
+pub type Float64 = f64;
diff --git a/swift-rs/src-rs/types/string.rs b/swift-rs/src-rs/types/string.rs
new file mode 100644
index 0000000..3f6f86f
--- /dev/null
+++ b/swift-rs/src-rs/types/string.rs
@@ -0,0 +1,84 @@
+use std::{
+ fmt::{Display, Error, Formatter},
+ ops::Deref,
+};
+
+use crate::{
+ swift::{self, SwiftObject},
+ Int, SRData, SRObject,
+};
+
+/// String type that can be shared between Swift and Rust.
+///
+/// ```rust
+/// use swift_rs::{swift, SRString};
+///
+/// swift!(fn get_greeting(name: &SRString) -> SRString);
+///
+/// let greeting = unsafe { get_greeting(&"Brendan".into()) };
+///
+/// assert_eq!(greeting.as_str(), "Hello Brendan!");
+/// ```
+/// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L56)
+#[repr(transparent)]
+pub struct SRString(SRData);
+
+impl SRString {
+ pub fn as_str(&self) -> &str {
+ unsafe { std::str::from_utf8_unchecked(&self.0) }
+ }
+}
+
+impl SwiftObject for SRString {
+ type Shape = ::Shape;
+
+ fn get_object(&self) -> &SRObject {
+ self.0.get_object()
+ }
+}
+
+impl Deref for SRString {
+ type Target = str;
+
+ fn deref(&self) -> &Self::Target {
+ self.as_str()
+ }
+}
+
+impl AsRef<[u8]> for SRString {
+ fn as_ref(&self) -> &[u8] {
+ self.0.as_ref()
+ }
+}
+
+impl From<&str> for SRString {
+ fn from(string: &str) -> Self {
+ unsafe { swift::string_from_bytes(string.as_ptr(), string.len() as Int) }
+ }
+}
+
+impl Display for SRString {
+ fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
+ self.as_str().fmt(f)
+ }
+}
+
+#[cfg(feature = "serde")]
+impl serde::Serialize for SRString {
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: serde::Serializer,
+ {
+ serializer.serialize_str(self.as_str())
+ }
+}
+#[cfg(feature = "serde")]
+impl<'a> serde::Deserialize<'a> for SRString {
+ fn deserialize(deserializer: D) -> Result
+ where
+ D: serde::Deserializer<'a>,
+ {
+ let string = String::deserialize(deserializer)?;
+ Ok(SRString::from(string.as_str()))
+ }
+}
diff --git a/swift-rs/src-swift/lib.swift b/swift-rs/src-swift/lib.swift
new file mode 100644
index 0000000..7bee3ad
--- /dev/null
+++ b/swift-rs/src-swift/lib.swift
@@ -0,0 +1,94 @@
+import Foundation
+
+public class SRArray: NSObject {
+ // Used by Rust
+ let pointer: UnsafePointer
+ let length: Int;
+
+ // Actual array, deallocates objects inside automatically
+ let array: [T];
+
+ public override init() {
+ self.array = [];
+ self.pointer = UnsafePointer(self.array);
+ self.length = 0;
+ }
+
+ public init(_ data: [T]) {
+ self.array = data;
+ self.pointer = UnsafePointer(self.array)
+ self.length = data.count
+ }
+
+ public func toArray() -> [T] {
+ return Array(self.array)
+ }
+}
+
+public class SRObjectArray: NSObject {
+ let data: SRArray
+
+ public init(_ data: [NSObject]) {
+ self.data = SRArray(data)
+ }
+}
+
+public class SRData: NSObject {
+ let data: SRArray
+
+ public override init() {
+ self.data = SRArray()
+ }
+
+ public init(_ data: [UInt8]) {
+ self.data = SRArray(data)
+ }
+
+ public init (_ srArray: SRArray) {
+ self.data = srArray
+ }
+
+ public func toArray() -> [UInt8] {
+ return self.data.toArray()
+ }
+}
+
+public class SRString: SRData {
+ public override init() {
+ super.init([])
+ }
+
+ public init(_ string: String) {
+ super.init(Array(string.utf8))
+ }
+
+ init(_ data: SRData) {
+ super.init(data.data)
+ }
+
+ public func toString() -> String {
+ return String(bytes: self.data.array, encoding: .utf8)!
+ }
+}
+
+@_cdecl("retain_object")
+func retainObject(ptr: UnsafeMutableRawPointer) {
+ let _ = Unmanaged.fromOpaque(ptr).retain()
+}
+
+@_cdecl("release_object")
+func releaseObject(ptr: UnsafeMutableRawPointer) {
+ let _ = Unmanaged.fromOpaque(ptr).release()
+}
+
+@_cdecl("data_from_bytes")
+func dataFromBytes(data: UnsafePointer, size: Int) -> SRData {
+ let buffer = UnsafeBufferPointer(start: data, count: size)
+ return SRData(Array(buffer))
+}
+
+@_cdecl("string_from_bytes")
+func stringFromBytes(data: UnsafePointer, size: Int) -> SRString {
+ let data = dataFromBytes(data: data, size: size);
+ return SRString(data)
+}
diff --git a/swift-rs/tests/swift-pkg/Package.swift b/swift-rs/tests/swift-pkg/Package.swift
new file mode 100644
index 0000000..d34291b
--- /dev/null
+++ b/swift-rs/tests/swift-pkg/Package.swift
@@ -0,0 +1,31 @@
+// swift-tools-version:5.3
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "test-swift",
+ platforms: [
+ .macOS(.v11),
+ ],
+ products: [
+ // Products define the executables and libraries a package produces, and make them visible to other packages.
+ .library(
+ name: "test-swift",
+ type: .static,
+ targets: ["test-swift"]),
+ ],
+ dependencies: [
+ // Dependencies declare other packages that this package depends on.
+ .package(name: "SwiftRs", path: "../../")
+ ],
+ targets: [
+ // Targets are the basic building blocks of a package. A target can define a module or a test suite.
+ // Targets can depend on other targets in this package, and on products in packages this package depends on.
+ .target(
+ name: "test-swift",
+ dependencies: [.product(name: "SwiftRs", package: "SwiftRs")],
+ path: ".",
+ exclude: ["test_example.rs", "test_bindings.rs"])
+ ]
+)
diff --git a/swift-rs/tests/swift-pkg/doctests.swift b/swift-rs/tests/swift-pkg/doctests.swift
new file mode 100644
index 0000000..11b28ca
--- /dev/null
+++ b/swift-rs/tests/swift-pkg/doctests.swift
@@ -0,0 +1,70 @@
+import Foundation
+import SwiftRs
+
+// SRArray
+//
+// Notice that IntArray and ArrayStruct are almost identical!
+// The only actual difference between these types is how they're used in Rust,
+// but if you added more fields to ArrayStruct then that wouldn't be the case anymore.
+
+class IntArray: NSObject {
+ var data: SRArray
+
+ init(data: [Int]) {
+ self.data = SRArray(data)
+ }
+}
+
+@_cdecl("get_int_array")
+func getIntArray() -> IntArray {
+ return IntArray(data: [1, 2, 3])
+}
+
+class ArrayStruct: NSObject {
+ var array: SRArray
+
+ init(array: [Int]) {
+ self.array = SRArray(array)
+ }
+}
+
+@_cdecl("get_array_struct")
+func getArrayStruct() -> ArrayStruct {
+ return ArrayStruct(array: [4, 5, 6])
+}
+
+// SRObject
+
+class CustomObject: NSObject {
+ var a: Int
+ var b: Bool
+
+ init(a: Int, b: Bool) {
+ self.a = a
+ self.b = b
+ }
+}
+
+@_cdecl("get_custom_object")
+func getCustomObject() -> CustomObject {
+ return CustomObject(a: 3, b: true)
+}
+
+// SRString
+
+@_cdecl("get_greeting")
+func getGreeting(name: SRString) -> SRString {
+ return SRString("Hello \(name.toString())!")
+}
+
+@_cdecl("echo")
+func echo(string: SRString) -> SRString {
+ return string
+}
+
+// SRData
+
+@_cdecl("get_data")
+func getData() -> SRData {
+ return SRData([1, 2, 3])
+}
diff --git a/swift-rs/tests/swift-pkg/lib.swift b/swift-rs/tests/swift-pkg/lib.swift
new file mode 100644
index 0000000..5f0b2bd
--- /dev/null
+++ b/swift-rs/tests/swift-pkg/lib.swift
@@ -0,0 +1,29 @@
+import SwiftRs
+import Foundation
+
+class Complex: NSObject {
+ var a: SRString
+ var b: Int
+ var c: Bool
+
+ public init(a: SRString, b: Int, c: Bool) {
+ self.a = a
+ self.b = b
+ self.c = c
+ }
+}
+
+@_cdecl("complex_data")
+func complexData() -> SRObjectArray {
+ return SRObjectArray([
+ Complex(a: SRString("Brendan"), b: 0, c: true),
+ Complex(a: SRString("Amod"), b: 1, c: false),
+ Complex(a: SRString("Lucas"), b: 2, c: true),
+ Complex(a: SRString("Oscar"), b: 3, c: false),
+ ])
+}
+
+@_cdecl("echo_data")
+func echoData(data: SRData) -> SRData {
+ return SRData(data.toArray())
+}
diff --git a/swift-rs/tests/test_bindings.rs b/swift-rs/tests/test_bindings.rs
new file mode 100644
index 0000000..35f9b4c
--- /dev/null
+++ b/swift-rs/tests/test_bindings.rs
@@ -0,0 +1,150 @@
+//! Test for swift-rs bindings
+//!
+//! Needs to be run with the env var `TEST_SWIFT_RS=true`, to allow for
+//! the test swift code to be linked.
+
+use serial_test::serial;
+use std::{env, process::Command};
+use swift_rs::*;
+
+macro_rules! test_with_leaks {
+ ( $op:expr ) => {{
+ let leaks_env_var = "TEST_RUNNING_UNDER_LEAKS";
+ if env::var(leaks_env_var).unwrap_or_else(|_| "false".into()) == "true" {
+ let _ = $op();
+ } else {
+ // we run $op directly in the current process first, as leaks will not give
+ // us the exit code of $op, but only if memory leaks happened or not
+ $op();
+
+ // and now we run the above codepath under leaks monitoring
+ let exe = env::current_exe().unwrap();
+
+ // codesign the binary first, so that leaks can be run
+ let debug_plist = exe.parent().unwrap().join("debug.plist");
+ let plist_path = &debug_plist.to_string_lossy();
+ std::fs::write(&debug_plist, DEBUG_PLIST_XML.as_bytes()).unwrap();
+ let status = Command::new("codesign")
+ .args([
+ "-s",
+ "-",
+ "-v",
+ "-f",
+ "--entitlements",
+ plist_path,
+ &exe.to_string_lossy(),
+ ])
+ .status()
+ .expect("cmd failure");
+ assert!(status.success(), "failed to codesign");
+
+ // run leaks command to detect memory leaks
+ let status = Command::new("leaks")
+ .args(["-atExit", "--", &exe.to_string_lossy(), "--nocapture"])
+ .env(leaks_env_var, "true")
+ .status()
+ .expect("cmd failure");
+ assert!(status.success(), "leaks detected in memory pressure test");
+ }
+ }};
+}
+
+swift!(fn echo(string: &SRString) -> SRString);
+
+#[test]
+#[serial]
+fn test_reflection() {
+ test_with_leaks!(|| {
+ // create memory pressure
+ let name: SRString = "Brendan".into();
+ for _ in 0..10_000 {
+ let reflected = unsafe { echo(&name) };
+ assert_eq!(name.as_str(), reflected.as_str());
+ }
+ });
+}
+
+swift!(fn get_greeting(name: &SRString) -> SRString);
+
+#[test]
+#[serial]
+fn test_string() {
+ test_with_leaks!(|| {
+ let name: SRString = "Brendan".into();
+ let greeting = unsafe { get_greeting(&name) };
+ assert_eq!(greeting.as_str(), "Hello Brendan!");
+ });
+}
+
+#[test]
+#[serial]
+fn test_memory_pressure() {
+ test_with_leaks!(|| {
+ // create memory pressure
+ let name: SRString = "Brendan".into();
+ for _ in 0..10_000 {
+ let greeting = unsafe { get_greeting(&name) };
+ assert_eq!(greeting.as_str(), "Hello Brendan!");
+ }
+ });
+}
+
+#[test]
+#[serial]
+fn test_autoreleasepool() {
+ test_with_leaks!(|| {
+ // create memory pressure
+ let name: SRString = "Brendan".into();
+ for _ in 0..10_000 {
+ autoreleasepool!({
+ let greeting = unsafe { get_greeting(&name) };
+ assert_eq!(greeting.as_str(), "Hello Brendan!");
+ });
+ }
+ });
+}
+
+#[repr(C)]
+struct Complex {
+ a: SRString,
+ b: Int,
+ c: Bool,
+}
+
+swift!(fn complex_data() -> SRObjectArray);
+
+#[test]
+#[serial]
+fn test_complex() {
+ test_with_leaks!(|| {
+ let mut v = vec![];
+
+ for _ in 0..10_000 {
+ let data = unsafe { complex_data() };
+ assert_eq!(data[0].a.as_str(), "Brendan");
+ v.push(data);
+ }
+ });
+}
+
+swift!(fn echo_data(data: &SRData) -> SRData);
+
+#[test]
+#[serial]
+fn test_data() {
+ test_with_leaks!(|| {
+ let str: &str = "hello";
+ let bytes = str.as_bytes();
+ for _ in 0..10_000 {
+ let data = unsafe { echo_data(&bytes.into()) };
+ assert_eq!(data.as_slice(), bytes);
+ }
+ });
+}
+
+const DEBUG_PLIST_XML: &str = r#"
+
+
+ com.apple.security.get-task-allow
+
+"#;
diff --git a/swift/se.swift b/swift/se.swift
index da0b37b..13942da 100644
--- a/swift/se.swift
+++ b/swift/se.swift
@@ -12,14 +12,18 @@ func isSupportSecureEnclave() -> Bool {
return SecureEnclave.isAvailable
}
+@_cdecl("print_greeting")
+func printGreeting(modifier: UnsafePointer) {
+ print("Hello \(String(cString: modifier))World!")
+}
+
enum StringError: Error {
case base64error
}
func generateKeyPair() throws {
var error: Unmanaged? = nil;
- guard
- let accessCtrl = SecAccessControlCreateWithFlags(
+ guard let accessCtrl = SecAccessControlCreateWithFlags(
nil,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
[.privateKeyUsage, .biometryCurrentSet],