Merge pull request 'feat: v1.9.0, remove swiftc dependency' (#6) from use-swift-se-tool into master

Reviewed-on: #6
This commit was merged in pull request #6.
This commit is contained in:
2025-03-24 00:36:07 +08:00
31 changed files with 36 additions and 2557 deletions

16
Cargo.lock generated
View File

@@ -1715,14 +1715,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "swift-rs"
version = "1.0.7"
name = "swift-secure-enclave-tool-rs"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7"
checksum = "1cc8ba06af10bf59b2f47fb46238a0c62c2ac927184253b4fb998b3b3f68950a"
dependencies = [
"base64 0.21.7",
"serde",
"serde_json",
"base64 0.22.1",
"hex",
"rust_util",
]
[[package]]
@@ -1880,7 +1880,7 @@ dependencies = [
[[package]]
name = "tiny-encrypt"
version = "1.8.5"
version = "1.9.0"
dependencies = [
"aes-gcm-stream",
"base64 0.22.1",
@@ -1911,7 +1911,7 @@ dependencies = [
"serde_json",
"simpledateformat",
"spki",
"swift-rs",
"swift-secure-enclave-tool-rs",
"tabled",
"x25519-dalek",
"x509-parser",

View File

@@ -1,6 +1,6 @@
[package]
name = "tiny-encrypt"
version = "1.8.5"
version = "1.9.0"
edition = "2021"
license = "MIT"
description = "A simple and tiny file encrypt tool"
@@ -10,11 +10,10 @@ repository = "https://git.hatter.ink/hatter/tiny-encrypt-rs"
[features]
default = ["decrypt", "macos", "smartcard"]
full-features = ["decrypt", "macos", "smartcard", "secure-enclave"]
full-features = ["decrypt", "macos", "smartcard"]
decrypt = ["smartcard"]
smartcard = ["openpgp-card", "openpgp-card-pcsc", "yubikey"]
macos = ["security-framework"]
secure-enclave = ["macos", "swift-rs"]
[dependencies]
aes-gcm-stream = "0.2"
@@ -45,7 +44,6 @@ x25519-dalek = { version = "2.0", features = ["static_secrets", "getrandom"] }
x509-parser = "0.16"
yubikey = { version = "0.8", features = ["untested"], optional = true }
zeroize = "1.7"
swift-rs = { version = "1.0.7", optional = true }
spki = "0.7"
pqcrypto-kyber = "0.8"
pqcrypto-traits = "0.3"
@@ -53,9 +51,7 @@ pinentry = "0.6"
secrecy = "0.10"
dialoguer = "0.11"
ctrlc = "3.4"
[build-dependencies]
swift-rs = { version = "1.0.7", features = ["build"], optional = true }
swift-secure-enclave-tool-rs = "0.1.0"
[profile.release]
codegen-units = 1

View File

@@ -1,11 +0,0 @@
#[cfg(feature = "secure-enclave")]
use swift_rs::SwiftLinker;
fn main() {
// Ensure this matches the versions set in your `Package.swift` file.
#[cfg(feature = "secure-enclave")]
SwiftLinker::new("11")
.with_ios("11")
.with_package("swift-lib", "./swift-lib/")
.link();
}

View File

@@ -37,7 +37,6 @@ use crate::crypto_cryptor::{Cryptor, KeyNonce};
use crate::spec::{EncEncryptedMeta, TinyEncryptEnvelop, TinyEncryptEnvelopType, TinyEncryptMeta};
use crate::util::SecVec;
use crate::util_digest::DigestWrite;
#[cfg(feature = "secure-enclave")]
use crate::util_keychainkey;
#[cfg(feature = "macos")]
use crate::util_keychainstatic;
@@ -466,7 +465,6 @@ pub fn try_decrypt_key(config: &Option<TinyEncryptConfig>,
#[cfg(feature = "macos")]
TinyEncryptEnvelopType::StaticX25519 => try_decrypt_key_ecdh_static_x25519(config, envelop),
TinyEncryptEnvelopType::PivP256 | TinyEncryptEnvelopType::PivP384 => try_decrypt_piv_key_ecdh(config, envelop, pin, slot, silent),
#[cfg(feature = "secure-enclave")]
TinyEncryptEnvelopType::KeyP256 => try_decrypt_se_key_ecdh(config, envelop),
TinyEncryptEnvelopType::PivRsa => try_decrypt_piv_key_rsa(config, envelop, pin, slot, silent),
#[cfg(feature = "macos")]
@@ -553,7 +551,6 @@ fn try_decrypt_piv_key_rsa(config: &Option<TinyEncryptConfig>,
Ok(after_2nd_0_bytes)
}
#[cfg(feature = "secure-enclave")]
fn try_decrypt_se_key_ecdh(config: &Option<TinyEncryptConfig>,
envelop: &TinyEncryptEnvelop) -> XResult<Vec<u8>> {
let wrap_key = WrapKey::parse(&envelop.encrypted_key)?;

View File

@@ -4,7 +4,6 @@ use rust_util::{debugging, information, opt_result, simple_error, success, warni
use crate::config::TinyEncryptConfigEnvelop;
use crate::spec::TinyEncryptEnvelopType;
#[cfg(feature = "secure-enclave")]
use crate::util_keychainkey;
use crate::util_keychainstatic;
use crate::util_keychainstatic::{KeychainKey, KeychainStaticSecret, KeychainStaticSecretAlgorithm};
@@ -40,16 +39,12 @@ const DEFAULT_SERVICE_NAME: &str = "tiny-encrypt";
pub fn init_keychain(cmd_init_keychain: CmdInitKeychain) -> XResult<()> {
if cmd_init_keychain.secure_enclave {
#[cfg(feature = "secure-enclave")]
return keychain_key_se(cmd_init_keychain);
#[cfg(not(feature = "secure-enclave"))]
return simple_error!("Feature secure-enclave is not built");
keychain_key_se(cmd_init_keychain)
} else {
keychain_key_static(cmd_init_keychain)
}
}
#[cfg(feature = "secure-enclave")]
pub fn keychain_key_se(cmd_init_keychain: CmdInitKeychain) -> XResult<()> {
if !util_keychainkey::is_support_se() {
return simple_error!("Secure enclave is not supported.");

View File

@@ -1,8 +1,7 @@
use clap::Args;
use rust_util::XResult;
use rust_util::{iff, XResult};
use crate::util;
#[cfg(feature = "secure-enclave")]
use crate::util_keychainkey;
#[derive(Debug, Args)]
@@ -16,13 +15,12 @@ pub fn version(_cmd_version: CmdVersion) -> XResult<()> {
features.push("macos".to_string());
#[cfg(feature = "smartcard")]
features.push("smartcard".to_string());
#[cfg(feature = "secure-enclave")]
features.push(format!("secure-enclave{}", rust_util::iff!(util_keychainkey::is_support_se(), "*", "")));
if features.is_empty() { features.push("-".to_string()); }
println!(
"User-Agent: {} [with features: {}]\n{}",
"User-Agent: {} [with features: {}]{}\n{}",
util::get_user_agent(),
features.join(", "),
iff!(util_keychainkey::is_support_se(), " with Secure Enclave Supported", ""),
env!("CARGO_PKG_DESCRIPTION")
);
Ok(())

View File

@@ -73,7 +73,6 @@ mod cmd_initpiv;
mod util_keychainstatic;
#[cfg(feature = "decrypt")]
mod cmd_execenv;
#[cfg(feature = "secure-enclave")]
mod util_keychainkey;
mod util_simple_pbe;

View File

@@ -1,53 +1,32 @@
use rust_util::{opt_result, simple_error, XResult};
use swift_rs::{Bool, SRString};
use swift_rs::swift;
use crate::util;
swift!(fn is_support_secure_enclave() -> Bool);
swift!(fn generate_secure_enclave_p256_keypair() -> SRString);
swift!(fn compute_secure_enclave_p256_ecdh(private_key_base64: SRString, ephemera_public_key_base64: SRString) -> SRString);
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use rust_util::{simple_error, XResult};
use swift_secure_enclave_tool_rs::KeyPurpose;
pub fn is_support_se() -> bool {
unsafe { is_support_secure_enclave() }
swift_secure_enclave_tool_rs::is_secure_enclave_supported().unwrap_or(false)
}
pub fn decrypt_data(private_key_base64: &str, ephemeral_public_key_bytes: &[u8]) -> XResult<Vec<u8>> {
let ephemera_public_key_base64 = util::encode_base64(ephemeral_public_key_bytes);
let result = unsafe {
compute_secure_enclave_p256_ecdh(
SRString::from(private_key_base64), SRString::from(ephemera_public_key_base64.as_str()),
)
};
let result = result.as_str();
if !result.starts_with("ok:SharedSecret:") {
return simple_error!("ECDH P256 in secure enclave failed: {}", result);
}
let shared_secret_hex = result.chars().skip("ok:SharedSecret:".len()).collect::<String>();
let shared_secret_hex = shared_secret_hex.trim();
Ok(opt_result!(hex::decode(shared_secret_hex), "Decrypt shared secret hex: {}, failed: {}", shared_secret_hex))
pub fn decrypt_data(
private_key_base64: &str,
ephemeral_public_key_bytes: &[u8],
) -> XResult<Vec<u8>> {
let private_key_representation = STANDARD.decode(private_key_base64)?;
let shared_secret = swift_secure_enclave_tool_rs::private_key_ecdh(
&private_key_representation,
ephemeral_public_key_bytes,
)?;
Ok(shared_secret)
}
pub fn generate_se_p256_keypair() -> XResult<(String, String)> {
if !is_support_se() {
return simple_error!("Secure enclave is not supported.");
}
let result = unsafe { generate_secure_enclave_p256_keypair() };
let result = result.as_str();
if !result.starts_with("ok:") {
return simple_error!("Generate P256 in secure enclave failed: {}", result);
}
let public_key_and_private_key = result.chars().skip(3).collect::<String>();
let public_key_and_private_keys = public_key_and_private_key.split(',').collect::<Vec<_>>();
if public_key_and_private_keys.len() != 2 {
return simple_error!("Generate P256 in secure enclave result is bad: {}", public_key_and_private_key);
}
let public_key = hex::encode(
opt_result!(util::decode_base64(public_key_and_private_keys[0]), "Public key is not base64 encoded: {}"));
let private_key = public_key_and_private_keys[1].to_string();
Ok((public_key, private_key))
let key_material =
swift_secure_enclave_tool_rs::generate_ecdsa_keypair(KeyPurpose::KeyAgreement, true)?;
Ok((
hex::encode(&key_material.public_key_point),
STANDARD.encode(&key_material.private_key_representation),
))
}

View File

@@ -1,30 +0,0 @@
// 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(.v11), // 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: "../swift-rs")
],
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")
]
)

View File

@@ -1,64 +0,0 @@
import SwiftRs
import CryptoKit
import LocalAuthentication
// reference:
// https://zenn.dev/iceman/scraps/380f69137c7ea2
// https://www.andyibanez.com/posts/cryptokit-secure-enclave/
@_cdecl("is_support_secure_enclave")
func isSupportSecureEnclave() -> Bool {
return SecureEnclave.isAvailable
}
@_cdecl("generate_secure_enclave_p256_keypair")
func generateSecureEnclaveP256KeyPair() -> SRString {
var error: Unmanaged<CFError>? = nil;
guard let accessCtrl = SecAccessControlCreateWithFlags(
nil,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
[.privateKeyUsage, .biometryCurrentSet],
&error
) else {
return SRString("err:\(error.debugDescription)")
}
do {
let privateKeyReference = try SecureEnclave.P256.KeyAgreement.PrivateKey.init(
accessControl: accessCtrl
);
let publicKeyBase64 = privateKeyReference.publicKey.x963Representation.base64EncodedString()
let dataRepresentationBase64 = privateKeyReference.dataRepresentation.base64EncodedString()
return SRString("ok:\(publicKeyBase64),\(dataRepresentationBase64)")
} catch {
return SRString("err:\(error)")
}
}
@_cdecl("compute_secure_enclave_p256_ecdh")
func computeSecureEnclaveP256Ecdh(privateKeyDataRepresentation: SRString, ephemeraPublicKey: SRString) -> SRString {
guard let privateKeyDataRepresentation = Data(
base64Encoded: privateKeyDataRepresentation.toString()
) else {
return SRString("err:private key base64 decode failed")
}
guard let ephemeralPublicKeyRepresentation = Data(
base64Encoded: ephemeraPublicKey.toString()
) else {
return SRString("err:ephemeral public key base64 decode failed")
}
do {
let context = LAContext();
let p = try SecureEnclave.P256.KeyAgreement.PrivateKey(
dataRepresentation: privateKeyDataRepresentation,
authenticationContext: context
)
let ephemeralPublicKey = try P256.KeyAgreement.PublicKey.init(derRepresentation: ephemeralPublicKeyRepresentation)
let sharedSecret = try p.sharedSecretFromKeyAgreement(
with: ephemeralPublicKey)
return SRString("ok:\(sharedSecret.description)")
} catch {
return SRString("err:\(error)")
}
}

417
swift-rs/Cargo.lock generated
View File

@@ -1,417 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "dashmap"
version = "5.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
dependencies = [
"cfg-if",
"hashbrown",
"lock_api",
"once_cell",
"parking_lot_core",
]
[[package]]
name = "futures"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-executor"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-sink"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
name = "futures-task"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-util"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.164"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "once_cell"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "parking_lot"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
]
[[package]]
name = "pin-project-lite"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro2"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
dependencies = [
"bitflags",
]
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.215"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]]
name = "serde_json"
version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "serial_test"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c789ec87f4687d022a2405cf46e0cd6284889f1839de292cadeb6c6019506f2"
dependencies = [
"dashmap",
"futures",
"lazy_static",
"log",
"parking_lot",
"serial_test_derive",
]
[[package]]
name = "serial_test_derive"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b64f9e531ce97c88b4778aad0ceee079216071cffec6ac9b904277f8f92e7fe3"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "swift-rs-hatter-fork"
version = "1.0.6"
dependencies = [
"base64",
"serde",
"serde_json",
"serial_test",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

View File

@@ -1,34 +0,0 @@
[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.22"
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"]

View File

@@ -1,201 +0,0 @@
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.

View File

@@ -1,19 +0,0 @@
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.

View File

@@ -1,30 +0,0 @@
// 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")
]
)

View File

@@ -1,483 +0,0 @@
# swift-rs
![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)
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 <b>all</b> 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
}
}
```
<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)
}
```
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<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:
```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<SquareNumberResult>);
```
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<T>` type!
```rust
use swift_rs::{swift, Bool, SRString};
swift!(optional_string(return_nil: Bool) -> Option<SRString>)
```
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<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.

View File

@@ -1,26 +0,0 @@
/// 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
}};
}

View File

@@ -1,326 +0,0 @@
#![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<String>,
runtime_library_import_paths: Vec<String>,
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<SwiftPackage>,
macos_min_version: String,
ios_min_version: Option<String>,
}
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<Path>) -> 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");
}

View File

@@ -1,90 +0,0 @@
/// 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<ValueArity>> {
// v.into_iter().fold(
// BTreeMap::<_, Vec<ValueArity>>::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::<Vec<_>>();
// 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::<Vec<_>>();
// 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)
// }
// }
// });
// }

View File

@@ -1,20 +0,0 @@
//! 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::*;

View File

@@ -1,101 +0,0 @@
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<T::Shape>);
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<Self::Shape>;
/// 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<Self>
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
}
};
}

View File

@@ -1,75 +0,0 @@
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<T>, SRArray<T>, 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()
}
}

View File

@@ -1,55 +0,0 @@
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<T: SwiftObject> SwiftRet for Option<T> {
unsafe fn retain(&self) {
if let Some(v) = self {
v.retain()
}
}
}
impl<T: SwiftObject> SwiftRet for T {
unsafe fn retain(&self) {
(*self).retain()
}
}

View File

@@ -1,20 +0,0 @@
//! 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();
}
}

View File

@@ -1,110 +0,0 @@
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<T> = SRObject<SRArray<SRObject<T>>>;
#[doc(hidden)]
#[repr(C)]
pub struct SRArrayImpl<T> {
data: NonNull<T>,
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<SRArray<Int>>);
///
/// 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<Int>
/// }
///
/// swift!(fn get_array_struct() -> SRObject<ArrayStruct>);
///
/// 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<T>(SRObject<SRArrayImpl<T>>);
impl<T> SRArray<T> {
pub fn as_slice(&self) -> &[T] {
self.0.as_slice()
}
}
impl<T> SwiftObject for SRArray<T> {
type Shape = SRArrayImpl<T>;
fn get_object(&self) -> &SRObject<Self::Shape> {
&self.0
}
}
impl<T> Deref for SRArray<T> {
type Target = [T];
fn deref(&self) -> &Self::Target {
self.0.as_slice()
}
}
impl<T> SRArrayImpl<T> {
pub fn as_slice(&self) -> &[T] {
unsafe { std::slice::from_raw_parts(self.data.as_ref(), self.length) }
}
}
#[cfg(feature = "serde")]
impl<T> serde::Serialize for SRArray<T>
where
T: serde::Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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()
}
}

View File

@@ -1,75 +0,0 @@
use crate::{
swift::{self, SwiftObject},
Int,
};
use super::{array::SRArray, SRObject};
use std::ops::Deref;
type Data = SRArray<u8>;
/// 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<Data>);
impl SRData {
///
pub fn as_slice(&self) -> &[u8] {
self
}
pub fn to_vec(&self) -> Vec<u8> {
self.as_slice().to_vec()
}
}
impl SwiftObject for SRData {
type Shape = Data;
fn get_object(&self) -> &SRObject<Self::Shape> {
&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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_bytes(self)
}
}

View File

@@ -1,11 +0,0 @@
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::*;

View File

@@ -1,75 +0,0 @@
use crate::swift::{self, SwiftObject};
use std::{ffi::c_void, ops::Deref, ptr::NonNull};
#[doc(hidden)]
#[repr(C)]
pub struct SRObjectImpl<T> {
_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<CustomObject>);
///
/// 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<T>(pub(crate) NonNull<SRObjectImpl<T>>);
impl<T> SwiftObject for SRObject<T> {
type Shape = T;
fn get_object(&self) -> &SRObject<Self::Shape> {
self
}
}
impl<T> Deref for SRObject<T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { &self.0.as_ref().data }
}
}
impl<T> AsRef<T> for SRObject<T> {
fn as_ref(&self) -> &T {
self
}
}
impl<T> Drop for SRObject<T> {
fn drop(&mut self) {
unsafe { swift::release_object(self.0.as_ref() as *const _ as *const c_void) }
}
}
#[cfg(feature = "serde")]
impl<T> serde::Serialize for SRObject<T>
where
T: serde::Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.deref().serialize(serializer)
}
}

View File

@@ -1,34 +0,0 @@
/// 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;

View File

@@ -1,84 +0,0 @@
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 = <SRData as SwiftObject>::Shape;
fn get_object(&self) -> &SRObject<Self::Shape> {
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
#[cfg(feature = "serde")]
impl<'a> serde::Deserialize<'a> for SRString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'a>,
{
let string = String::deserialize(deserializer)?;
Ok(SRString::from(string.as_str()))
}
}

View File

@@ -1,94 +0,0 @@
import Foundation
public class SRArray<T>: NSObject {
// Used by Rust
let pointer: UnsafePointer<T>
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<NSObject>
public init(_ data: [NSObject]) {
self.data = SRArray(data)
}
}
public class SRData: NSObject {
let data: SRArray<UInt8>
public override init() {
self.data = SRArray()
}
public init(_ data: [UInt8]) {
self.data = SRArray(data)
}
public init (_ srArray: SRArray<UInt8>) {
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<AnyObject>.fromOpaque(ptr).retain()
}
@_cdecl("release_object")
func releaseObject(ptr: UnsafeMutableRawPointer) {
let _ = Unmanaged<AnyObject>.fromOpaque(ptr).release()
}
@_cdecl("data_from_bytes")
func dataFromBytes(data: UnsafePointer<UInt8>, size: Int) -> SRData {
let buffer = UnsafeBufferPointer(start: data, count: size)
return SRData(Array(buffer))
}
@_cdecl("string_from_bytes")
func stringFromBytes(data: UnsafePointer<UInt8>, size: Int) -> SRString {
let data = dataFromBytes(data: data, size: size);
return SRString(data)
}