feat: revert readme

This commit is contained in:
2024-12-15 01:00:00 +08:00
parent bf9f228967
commit 0cda981409

625
README.md
View File

@@ -1,483 +1,238 @@
# swift-rs
# card-cli
![Crates.io](https://img.shields.io/crates/v/swift-rs?color=blue&style=flat-square)
![docs.rs](https://img.shields.io/docsrs/swift-rs?color=blue&style=flat-square)
> FIDO(U2F, WebAuthn), YubiKey, OpenPGP command line tool
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"] }
Install:
```shell
cargo install --git https://git.hatter.ink/hatter/card-cli.git
```
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"
)
],
)
]
)
Compile without features:
```shell
cargo build --release --no-default-features
```
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;
# PGP
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();
## encrypt & decrypt
// Other build steps
sample encrypt public key
```
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApUM8M+QRMUw0dIvXISFx
43j4h9CK38Y9HD6kPcc3Z0dCGPiFy7Ze0OQebPWHyUZ2YmqsdyzFuOQuV9P2pxxj
/WLIgRqZV8Jk8tWhtAjOOvm0MTc2rg+EJHfa+zhX4eFEMsj4DvQBMJDXiKnpXTM/
j7oMKpIUQHqfXBwsEJHLmHZTLeEBEYKcZXTAmuu3WdxK5jvEc02Xt2hZ1fBs0M9e
/2EMe3t69aH4/rabiBjF2h9Jde15wrJMxXaCCWJqYhbBS0CJ3BdjkAqOIpcqPXva
xiJN1pNpK8ejA9Q4Nmx7pxnvfv+hCPkWXZS3r/BWZ9lFZc8uErQEbB4gLgko8jOl
fQF7cYqtZEs69qY8nnIUBsqZYfAp+bQd2xCFSbEZAl+OrtGzfVjD9YFMPy02+xRg
v2N3KT3KHHvuU7WxrvffrshP2fwDuG2MBlmcq1suAKxA0cYPSyajceEqw/3ogSp7
7SYx41rT8EWLmTvU0CHzCsuf/O7sDWZRfxatAzWhBBhnKCPqzizpOQOqm8XhCt74
FfnabPpHM9XUjoQIPrTssyS3eWqynzJiAqez6v2LK2fhL7IkcLtvt5p59Y+KY4I6
YQ09iUh7lKJHRhkgTomUurJHieVHMWFGIHofEC+nU6pGIUh0P7Nr0Gz45GJTwWGd
hW53WfImja+b5kwwyqUikyMCAwEAAQ==
-----END PUBLIC KEY-----
```
encrypt
```shell
$ openssl rsautl -encrypt -pubin -inkey enc_key.pem -in test.txt -out enc.txt -pkcs
OR
$ openssl pkeyutl -encrypt -inkey enc_key.pem -pubin -in a.txt -out enc.txt
```
decrypt
```shell
$ card-cli pgp-card-decrypt -c $(cat enc.txt | xxd -ps -c 11111)
OR
$ card-cli piv-decrypt -s r3 -c "$(cat enc.txt | base64)"
```
## sign & verify
sign
```shell
$ card-cli pgp-card-sign -2 $(shasum -a 256 test.txt | awk '{print $1}')
OR
$ card-cli pgp-card-sign --in test.txt --use-sha256
```
verify
```shell
$ openssl dgst -sha256 -verify sign_key.pem -signature sig test.txt
Verified OK
```
# sample public keys
```
[INFO ] Authentication fingerprint: EB0A43A10BFC6E58323F7650BA42AE533FDCE10E
[INFO ] Authentication public key sha256: ac97c7f9f500f3fbab635536096311c62698f8c22abd9e9687de7893932bc15b
[INFO ] Authentication public key: -----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA8Kg5fg47YilT/xFOZ7xK
17T47cfwzS6L/4IRtTjcygvmOVSdOISihQxVfsygpxhThRQ3pjqhFGqH9LUIpry/
a8hWfPMZYolYywBvdx5S6UGDUeRf2zLcRYrQo+Fs9oxdhxPE05HhWl9L5ORn4HWz
RZSkNfh7PDKPJRUaJV85uB6Fyvt0GGY14pmINZ7NRLLi2ubYBlp3CLSh7XdleVE8
/Q6gya501INhXUksuwHXdPYtcXF3l+VIdMc6YJTxivFLtujqiEAfEwauuv+1GzsN
ZDOg6JfSc+1d7iZMixU4RrKtzM57ZwGX0bAK3MQdP6iT20DOYq/BDJTXJuhQBWgE
6pIDiTJF4q/If0ZLxU+kxstAEg0fuD+wOg/+4W1BSn5D3hSdvVOxgj3hWtPudAVp
QucP8LKnq5B0oy4LdGqXXAQYJ2Q+ln0N9By2T8N/P37HOsR7yJLl8cM2FptCoo4x
ViGzmIbir8EyZ6VQmoi8fqOP4x9nH5XeNA2JCVLEc0o6n5PJ4IitYYCb0NGOPTHV
FEz2qzxkQDJxS5oC7GddWQB/pa4Jq0EL9dEabB2oPyvYBAmmE0HzZWLl3T1kR1dJ
fAXuqgShFcZLXa1SFUpLzlJi3jARuxoaUeHnKP3xeAd8o5WPBwzXM7LL47nTueNa
uFZKwHs/e9x4EszQ/qFo2uECAwEAAQ==
-----END PUBLIC KEY-----
[INFO ] Encryption fingerprint: E48EC98FE6CAE85AAFD5A68AC37A909EAF1BFB00
[INFO ] Encryption public key sha256: de5a99c239a82adf039982cb6319abcb95f44cfc76a5027ae6f7819cfc5fde7c
[INFO ] Encryption public key: -----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApUM8M+QRMUw0dIvXISFx
43j4h9CK38Y9HD6kPcc3Z0dCGPiFy7Ze0OQebPWHyUZ2YmqsdyzFuOQuV9P2pxxj
/WLIgRqZV8Jk8tWhtAjOOvm0MTc2rg+EJHfa+zhX4eFEMsj4DvQBMJDXiKnpXTM/
j7oMKpIUQHqfXBwsEJHLmHZTLeEBEYKcZXTAmuu3WdxK5jvEc02Xt2hZ1fBs0M9e
/2EMe3t69aH4/rabiBjF2h9Jde15wrJMxXaCCWJqYhbBS0CJ3BdjkAqOIpcqPXva
xiJN1pNpK8ejA9Q4Nmx7pxnvfv+hCPkWXZS3r/BWZ9lFZc8uErQEbB4gLgko8jOl
fQF7cYqtZEs69qY8nnIUBsqZYfAp+bQd2xCFSbEZAl+OrtGzfVjD9YFMPy02+xRg
v2N3KT3KHHvuU7WxrvffrshP2fwDuG2MBlmcq1suAKxA0cYPSyajceEqw/3ogSp7
7SYx41rT8EWLmTvU0CHzCsuf/O7sDWZRfxatAzWhBBhnKCPqzizpOQOqm8XhCt74
FfnabPpHM9XUjoQIPrTssyS3eWqynzJiAqez6v2LK2fhL7IkcLtvt5p59Y+KY4I6
YQ09iUh7lKJHRhkgTomUurJHieVHMWFGIHofEC+nU6pGIUh0P7Nr0Gz45GJTwWGd
hW53WfImja+b5kwwyqUikyMCAwEAAQ==
-----END PUBLIC KEY-----
[INFO ] Signature fingerprint: 6FAFC0E0170985AA71545483C794B1646A886CD6
[INFO ] Signature public key sha256: d65831b0316a03828eeb31fe6a51e6eec59e7092eb6d3477404ad2f5fa08e903
[INFO ] Signature public key: -----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7kVYTHxFjZD9kT+w97B
GiHfqlyoulJ10cRqaWwX3/mZKfoeGJkBDglFnLfgtHhXivPqRSn73sCX6M0HCzSq
9M/drkms/H8cecM08SoZdZTM0TVr/c8w0ZA7Ipoder9K/9LdGpIgoc3qa8hdY2nH
TwGYJ53aQv32neOcg3p/vzqdzgmwbk4JLjcIMhOuTUj4xM8OMnkxRpyy9+Ghi22X
oZXDxu8meI2Pc8jM+zpRYb0wd06dd231m03CK80LAwSvIn7dGFAr+xTF5XKopXHY
vuT+9SshszbP4+pSqbEHZhJOX1/os+Uo8KKfysifJBKfKCVvVWho8QCWoXgiNuOJ
3cYoThfWwUpIS1S51el/fPPSk3K295jlZAON9yEszdzKHGVGOrtJ7e9XSxKIXqhG
us2XA14eMvhQdaOgd/bscXIYe4YzqvaqvVRiDUP8bzA+4w0ctB0w9HRFGK5lajTn
/QQvkKP9JQXm6Tb2GB+wjuU3wPXhKRWscEzbHVwMq2WiaYH5vWVhHI6lbqXcWkvZ
i2gZXQPyrAKzUau1Z2lBN2xi2cv5+9JJth5pHebuLOWbuf1WV4nR1fdSNdG7GGmj
G951w/1bTqIlzN4Vl6kdore4u45U4kO4Xf7Hq8b8k8ys107ENpgO7lB9KLoMMFKS
vjG+EPEF3g8ywKaS8mZQX+sCAwEAAQ==
-----END PUBLIC KEY-----
```
# piv-ecdh
```shell
$ card-cli piv-ecdh --public-256 --public-key-point-hex 04dd3eebd906c9cf00b08ec29f7ed61804d1cc1d1352d9257b628191e08fc3717c4fae3298cd5c4829cec8bf3a946e7db60b7857e1287f6a0bae6b3f2342f007d0 --json
{
"epk_point_hex": "04bbb6a458e81d2c646587118abfb029ff715db366f92a1d0468887f9947f176c11961eccebd5b9cbbb8b67e33fa8d3f0010a4aaf5010d0f419f1f99b4c2d7aa56",
"pk_point_hex": "04dd3eebd906c9cf00b08ec29f7ed61804d1cc1d1352d9257b628191e08fc3717c4fae3298cd5c4829cec8bf3a946e7db60b7857e1287f6a0bae6b3f2342f007d0",
"shared_secret_hex": "58069f1b2ce85c4f2232070567bef99f71b45f69ab321c4c782e599813b56f25"
}
$ card-cli piv-ecdh --private --slot 82 --epk 04bbb6a458e81d2c646587118abfb029ff715db366f92a1d0468887f9947f176c11961eccebd5b9cbbb8b67e33fa8d3f0010a4aaf5010d0f419f1f99b4c2d7aa56 --json
[WARN ] Get slot: 82 meta data failed
{
"shared_secret_hex": "58069f1b2ce85c4f2232070567bef99f71b45f69ab321c4c782e599813b56f25"
}
```
With those steps completed, you should be ready to start using Swift code from Rust!
# piv-ecsign
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`.
```shell
$ card-cli piv-ecsign -s 82 --hash-hex 8f25018489d6fe0dec34a352314c38dc146247b7de65735790f4398a92afa84b --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
{
"hash_hex": "8f25018489d6fe0dec34a352314c38dc146247b7de65735790f4398a92afa84b",
"signed_data_base64": "MEUCICdes5Y0Id7KBNL23ZsTXXXGAzmsWYyDa6szQwjCxhCJAiEAhJotD2dPK/fWNjNrwkrPd0F20MpGgIY3WiKDR7YgJbk=",
"signed_data_hex": "30450220275eb3963421deca04d2f6dd9b135d75c60339ac598c836bab334308c2c61089022100849a2d0f674f2bf7d636336bc24acf774176d0ca468086375a228347b62025b9",
"slot": "82"
}
```
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`.
# import private key to PIV card & generate certificate
```swift
@_cdecl("square_number")
public func squareNumber(number: Int) -> Int {
return number * number
}
```shell
$ ykman piv keys import --pin-policy NEVER --touch-policy CACHED 82 private_key.pem
```
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:
| Parameter | Description |
| ---- |------------------------------------------------------------------|
| --pin-policy | \[ DEFAULT \| NEVER \| ONCE \| ALWAYS \] PIN policy for slot |
| --touch-policy | \[ DEFAULT \| NEVER \| ALWAYS \| CACHED \] touch policy for slot |
```rust
use swift_rs::swift;
swift!(fn square_number(number: Int) -> Int);
```shell
$ ykman piv certificates generate 82 public_key.pem -s 'O=age-plugin-yubikey,OU=0.3.3,CN=hatter-yk'
```
Lastly, you can call the function from regular Rust functions.
Note that <b>all</b> calls to a Swift function are unsafe,
and require wrapping in an `unsafe {}` block or `unsafe fn`.
# age
```rust
fn main() {
let input: Int = 4;
let output = unsafe { square_number(input) };
## pgp-age-address
println!("Input: {}, Squared: {}", input, output);
// Prints "Input: 4, Squared: 16"
}
```shell
$ card-cli pgp-age-address
[INFO ] Found 1 card(s)
[OK ] Found card #0: Ok(ApplicationIdentifier { application: 1, version: 772, manufacturer: 6, serial: 370378374 })
[OK ] Age address: age10l464vxcpnkjguctvylnmp5jg4swhncn4quda0qxta3ud8pycc0qeaj2te
```
Check [the documentation](TODO) for all available helper types.
# sign-jwt
## 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
}
Sign a JWT:
```shell
card-cli sign-jwt -s r3 \
-C iss:****** \
-C sub:****** \
-C aud:client_gard****** \
-K KEY=ID \
--jti \
--validity 10m --json
```
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`:
# SSH CA
```swift
class SquareNumberResult: NSObject {
var input: Int
var output: Int
## Generate SSH root CA
init(_ input: Int, _ output: Int) {
self.input = input;
self.output = output
}
}
```shell
card-cli ssh-pub-key --ca -s r15
```
<sub><sup>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)
}
Outputs:
```
cert-authority,principals="root" ecdsa-sha2-nistp384 AAAAE2VjZHNh****** Yubikey-PIV-R15
```
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.
> `principals` can be multiple items, split by `,`, e.g. `root,hatterink`
This may sound daunting, but it's not actually a problem thanks to `SRObject<T>`.
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:
## Generate SSH user CA
```rust
use swift_rs::{swift, Int, SRObject};
```shell
ssh-keygen -f id_user
// 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<SquareNumberResult>);
card-cli ssh-piv-cert --pub id_user.pub -s r15
```
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
}
Show SSH CA cert details:
```shell
ssh-keygen -L -f id_user-cert.pub
```
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")
}
SSH to server:
```shell
ssh -i id_user root@example.com
```
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!
```rust
use swift_rs::{swift, Bool, SRString};
<br><br>
swift!(optional_string(return_nil: Bool) -> Option<SRString>)
```
Downloads:
* https://developers.yubico.com/yubikey-manager/
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!
<br>
## 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<T>`) 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<T>` 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<Int>
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<Int>
}
// Since IntArray extends NSObject in its Swift implementation,
// it must be wrapped in SRObject on the Rust side
swift!(fn get_numbers() -> SRObject<IntArray>);
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<usize>`,
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<SRArray<Int>>);
```
**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<T>` 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<T>`, 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<T> since
// SRObjectArray<T> does it automatically
swift!(fn get_tuples() -> SRObjectArray<IntTuple>);
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<T>` you like, just remember to follow the 3 rules!
## Bonuses
### SRData
A wrapper type for `SRArray<T>` 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.
Related projects:
* https://crates.io/crates/openpgp-card-tools
* https://github.com/sekey/sekey
* https://github.com/str4d/age-plugin-yubikey