Skip to content

Commit f805115

Browse files
committed
v0.3.0
1 parent 8b8aede commit f805115

File tree

7 files changed

+78
-49
lines changed

7 files changed

+78
-49
lines changed

README.md

+49-16
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,28 @@ Call Swift functions from Rust with ease!
44

55
## Setup
66

7-
After adding `swift-rs` to your project's `Cargo.toml`, some setup work must be done.
7+
Add `swift-rs` to your project's `dependencies` and `build-dependencies`:
8+
9+
```toml
10+
[dependencies]
11+
swift-rs = "0.3.0"
12+
13+
[build-dependencies]
14+
swift-rs = { version = "0.3.0", features = "build" }
15+
```
16+
17+
Next, some setup work must be done:
818

919
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.
10-
2. Add SwiftRs as a dependency to your Swift package. A quick internet search can show you how to do this.
20+
2. Add `SwiftRs` as a dependency to your Swift package. A quick internet search can show you how to do this.
1121
3. Create a `build.rs` file in your project's source folder, if you don't have one already.
1222
4. Link the swift runtime to your binary
1323

1424
```rust
15-
use swift_rs::build_utils;
25+
use swift_rs::build;
1626

1727
fn build() {
18-
build_utils::link_swift();
28+
build_utils::build();
1929

2030
// Other build steps
2131
}
@@ -24,11 +34,11 @@ fn build() {
2434
4. Link your swift package to your binary. `link_swift_package` takes 2 arguments: The name of your package as specified in its `Package.swift`, and the location of your package's root folder relative to your rust project's root folder.
2535

2636
```rust
27-
use swift_rs::build_utils;
37+
use swift_rs::build;
2838

2939
fn build() {
30-
build_utils::link_swift();
31-
build_utils::link_swift_package(PACKAGE_NAME, PACKAGE_PATH);
40+
build::link_swift();
41+
build::link_swift_package(PACKAGE_NAME, PACKAGE_PATH);
3242

3343
// Other build steps
3444
}
@@ -40,9 +50,9 @@ With those steps completed, you should be ready to start using Swift code from R
4050

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

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

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

@@ -103,7 +113,7 @@ struct SquareNumberResult {
103113
}
104114
```
105115

106-
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:
116+
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`:
107117

108118
```swift
109119
class SquareNumberResult: NSObject {
@@ -159,11 +169,35 @@ fn main() {
159169
let result = unsafe { square_number(input) };
160170

161171
let result_input = result.input; // 4
162-
let result_input = result.output; // 16
172+
let result_output = result.output; // 16
173+
}
174+
```
175+
176+
Creating objects in Rust and then passing them to Swift is not supported.
177+
178+
## Optionals
179+
180+
`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.
181+
182+
Let's say we have a function returning an optional `SRString`:
183+
184+
```swift
185+
@_cdecl("optional_string")
186+
func optionalString(returnNil: Bool) -> SRString? {
187+
if (returnNil) return nil
188+
else return SRString("lorem ipsum")
189+
}
190+
```
191+
192+
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<T>` type!
193+
194+
```rust
195+
extern "C" {
196+
fn optional_string(return_nil: bool) -> Option<SRString>
163197
}
164198
```
165199

166-
Currently, creating objects in Rust and then passing them to Swift is not supported.
200+
Null pointers are actually the reason why a function that returns an optional primitives cannot be represented in C. If this were to be supported, how could a `nil` be differentiated from a number? It can't!
167201

168202
## Complex types
169203

@@ -230,7 +264,7 @@ fn main() {
230264
// or though the Deref trait
231265
let value_str: &str = &*value_srstring;
232266

233-
// STString also implements Display
267+
// SRString also implements Display
234268
println!("{}", value_ststring); // Will print "lorem ipsum" to the console
235269
}
236270
```
@@ -381,11 +415,10 @@ Mutating values across Swift and Rust is not currently an aim for this library,
381415

382416
## Why?
383417

384-
I was helping my friend [Jamie Pine](https://twitter.com/jamiepine) with a desktop app made with [Tauri](https://twitter.com/TauriApps), an Electron alternative that uses Rust as its backend. One of the features Jamie wanted was to get the preview icon for files on his filesystem, which can be done with the [icon(forFile:)](https://developer.apple.com/documentation/appkit/nsworkspace/1528158-icon) function on the app's `NSWorkspace`. This requires accessing the static `shared` property of `NSWorkspace`, something that after some research wasn't possible using the [Rust Objective-C bindings](https://docs.rs/objc/0.2.7/objc/) (since from what I can tell it only supports sending and receiving messages, not accessing static properties), and I could figure out if [swift-bindgen](https://github.com/nvzqz/swift-bindgen) could do the job. So I created this library and the rest is history!
418+
I was helping my friend [Jamie Pine](https://twitter.com/jamiepine) with a desktop app made with [Tauri](https://twitter.com/TauriApps), an Electron alternative that uses Rust as its backend. One of the features Jamie wanted was to get the preview icon for files on his filesystem, which can be done with the [icon(forFile:)](https://developer.apple.com/documentation/appkit/nsworkspace/1528158-icon) function on the app's `NSWorkspace`. This requires accessing the static `shared` property of `NSWorkspace`, something that after some research wasn't possible using the [Rust Objective-C bindings](https://docs.rs/objc/0.2.7/objc/) (since from what I can tell it only supports sending and receiving messages, not accessing static properties), and I couldn't figure out if [swift-bindgen](https://github.com/nvzqz/swift-bindgen) could do the job. So I created this library and the rest is history!
385419

386-
The examples folder is actually the same Swift code that Jamie uses in his project. While there's probably other, less unsafe ways to interop with Swift, its been both my and Jamie's experience that leveraging Swift for it's native API access and Rust for building applications is quite nice compared to wrangling Swift with calls from Rust similar to how the `objc` crate has you do. This library probably has a littany of problems around memory management and leaks since I'm not that well versed in the Swift runtime, but it gets the job done!
420+
The examples folder is similar to the same Swift code that Jamie uses in his project. While there's probably other, less unsafe ways to interop with Swift, its been both my and Jamie's experience that leveraging Swift for it's native API access and Rust for building applications is quite nice compared to wrangling Swift with calls from Rust similar to how the `objc` crate has you do. This library probably has a littany of problems around memory management and leaks since I'm not that well versed in the Swift runtime, but it gets the job done!
387421

388422
## Todo
389423

390-
- Swift class deallocation from rust (implementing Drop and using deallocate\_{type} methods)
391424
- More ease of use and utility functions

example/src/build.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
use swift_rs::build_utils;
1+
use swift_rs::build;
22

33
fn main() {
4-
build_utils::link_swift();
5-
build_utils::link_swift_package("swift-lib", "./swift-lib/");
4+
build::link_swift();
5+
build::link_swift_package("swift-lib", "./swift-lib/");
66
}

example/swift-lib/src/lib.swift

+8-8
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import SwiftRs
22
import AppKit
33

44
@_cdecl("get_file_thumbnail_base64")
5-
public func getFileThumbnailBase64(path: SRString) -> SRString {
5+
func getFileThumbnailBase64(path: SRString) -> SRString {
66
let path = path.to_string();
77

88
let image = NSWorkspace.shared.icon(forFile: path)
@@ -11,7 +11,7 @@ public func getFileThumbnailBase64(path: SRString) -> SRString {
1111
return SRString(bitmap.base64EncodedString())
1212
}
1313

14-
public class Volume: NSObject {
14+
class Volume: NSObject {
1515
var name: SRString
1616
var path: SRString
1717
var total_capacity: Int
@@ -32,7 +32,7 @@ public class Volume: NSObject {
3232
}
3333

3434
@_cdecl("get_mounts")
35-
public func getMounts() -> SRObjectArray {
35+
func getMounts() -> SRObjectArray {
3636
let keys: [URLResourceKey] = [
3737
.volumeNameKey,
3838
.volumeIsRemovableKey,
@@ -42,7 +42,7 @@ public func getMounts() -> SRObjectArray {
4242
.volumeIsRootFileSystemKey,
4343
]
4444

45-
var paths = autoreleasepool {
45+
let paths = autoreleasepool {
4646
FileManager().mountedVolumeURLs(includingResourceValuesForKeys: keys, options: [])
4747
}
4848

@@ -77,8 +77,8 @@ public func getMounts() -> SRObjectArray {
7777
return SRObjectArray(validMounts)
7878
}
7979

80-
public class Test: NSObject {
81-
public var null: Bool
80+
class Test: NSObject {
81+
var null: Bool
8282

8383
public init(_ null: Bool)
8484
{
@@ -87,8 +87,8 @@ public class Test: NSObject {
8787
}
8888

8989
@_cdecl("return_nullable")
90-
public func returnNullable(null: Bool) -> Test? {
90+
func returnNullable(null: Bool) -> Test? {
9191
if (null == true) { return nil }
9292

9393
return Test(null)
94-
}
94+
}
File renamed without changes.

src-rs/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ pub mod types;
44
pub use types::*;
55

66
#[cfg(feature = "build")]
7-
pub mod build_utils;
7+
pub mod build;

src-rs/swift.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ use std::ffi::c_void;
33
use crate::types::SRString;
44

55
extern "C" {
6-
pub fn release_object(obj: *const c_void);
7-
pub fn allocate_string(data: *const u8, size: usize) -> SRString;
6+
pub(crate) fn release_object(obj: *const c_void);
7+
pub(crate) fn allocate_string(data: *const u8, size: usize) -> SRString;
88
}

src-swift/lib.swift

+15-19
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,43 @@ import Foundation
22

33
public class SRArray<T>: NSObject {
44
// Used by Rust
5-
public var pointer: UnsafePointer<T>
6-
public var length: Int
5+
let pointer: UnsafePointer<T>
6+
let length: Int;
77

88
// Actual array, deallocates objects inside automatically
9-
var _array: [T]
10-
9+
let array: [T];
10+
1111
public override init() {
12-
self._array = [];
13-
self.pointer = UnsafePointer(self._array);
12+
self.array = [];
13+
self.pointer = UnsafePointer(self.array);
1414
self.length = 0;
1515
}
16-
16+
1717
public init(_ data: [T]) {
18-
self._array = data;
19-
self.pointer = UnsafePointer(self._array)
18+
self.array = data;
19+
self.pointer = UnsafePointer(self.array)
2020
self.length = data.count
2121
}
2222
}
2323

2424
public class SRObjectArray: NSObject {
25-
var data: SRArray<NSObject>
25+
let data: SRArray<NSObject>
2626

2727
public init(_ data: [NSObject]) {
2828
self.data = SRArray(data)
2929
}
3030
}
3131

3232
public class SRData: NSObject {
33-
public var data: SRArray<UInt8>
33+
let data: SRArray<UInt8>
3434

3535
public override init() {
36-
self.data = SRArray<UInt8>.init()
36+
self.data = SRArray()
3737
}
3838

3939
public init(_ data: [UInt8]) {
4040
self.data = SRArray(data)
4141
}
42-
43-
public func to_data() -> Data {
44-
return Data(bytes: self.data.pointer, count: self.data.length)
45-
}
4642
}
4743

4844
public class SRString: SRData {
@@ -55,19 +51,19 @@ public class SRString: SRData {
5551
}
5652

5753
public func to_string() -> String {
58-
return String(bytes: self.to_data(), encoding: .utf8)!
54+
return String(bytes: self.data.array, encoding: .utf8)!
5955
}
6056
}
6157

6258
@_cdecl("allocate_string")
63-
public func allocateString(data: UnsafePointer<UInt8>, size: Int) -> SRString {
59+
func allocateString(data: UnsafePointer<UInt8>, size: Int) -> SRString {
6460
let buffer = UnsafeBufferPointer(start: data, count: size)
6561
let string = String(bytes: buffer, encoding: .utf8)!
6662
let ret = SRString(string)
6763
return ret
6864
}
6965

7066
@_cdecl("release_object")
71-
public func releaseObject(obj: UnsafePointer<AnyObject>) {
67+
func releaseObject(obj: UnsafePointer<AnyObject>) {
7268
let _ = Unmanaged.passUnretained(obj.pointee).release();
7369
}

0 commit comments

Comments
 (0)