feat: add crate swift-rs

This commit is contained in:
2023-12-09 10:17:41 +08:00
parent d36c06f461
commit 215b75d6da
35 changed files with 2489 additions and 2 deletions

View File

@@ -0,0 +1,26 @@
/// Run code with its own autorelease pool. Semantically, this is identical
/// to [`@autoreleasepool`](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html)
/// in Objective-C
///
///
/// ```no_run
/// use swift_rs::autoreleasepool;
///
/// autoreleasepool!({
/// // do something memory intensive stuff
/// })
/// ```
#[macro_export]
macro_rules! autoreleasepool {
( $expr:expr ) => {{
extern "C" {
fn objc_autoreleasePoolPush() -> *mut std::ffi::c_void;
fn objc_autoreleasePoolPop(context: *mut std::ffi::c_void);
}
let pool = unsafe { objc_autoreleasePoolPush() };
let r = { $expr };
unsafe { objc_autoreleasePoolPop(pool) };
r
}};
}

326
swift-rs/src-rs/build.rs Normal file
View File

@@ -0,0 +1,326 @@
#![allow(dead_code)]
use std::{env, fmt::Display, path::Path, path::PathBuf, process::Command};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct SwiftTarget {
triple: String,
unversioned_triple: String,
module_triple: String,
//pub swift_runtime_compatibility_version: String,
#[serde(rename = "librariesRequireRPath")]
libraries_require_rpath: bool,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct SwiftPaths {
runtime_library_paths: Vec<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

@@ -0,0 +1,90 @@
/// This retain-balancing algorithm is cool but likely isn't required.
/// I'm keeping it around in case it's necessary one day.
// #[derive(Clone, Copy, Debug)]
// enum ValueArity {
// Reference,
// Value,
// }
// pub unsafe fn balance_ptrs(args: Vec<(*const c_void, bool)>, ret: Vec<(*const c_void, bool)>) {
// fn collect_references(
// v: Vec<(*const c_void, bool)>,
// ) -> BTreeMap<*const c_void, Vec<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)
// }
// }
// });
// }

20
swift-rs/src-rs/lib.rs Normal file
View File

@@ -0,0 +1,20 @@
//! Call Swift functions from Rust with ease!
#![cfg_attr(docsrs, feature(doc_cfg))]
mod autorelease;
mod swift;
mod swift_arg;
mod swift_ret;
mod types;
pub use autorelease::*;
pub use swift::*;
pub use swift_arg::*;
pub use swift_ret::*;
pub use types::*;
#[cfg(feature = "build")]
#[cfg_attr(docsrs, doc(cfg(feature = "build")))]
mod build;
#[cfg(feature = "build")]
pub use build::*;

101
swift-rs/src-rs/swift.rs Normal file
View File

@@ -0,0 +1,101 @@
use std::ffi::c_void;
use crate::*;
/// Reference to an `NSObject` for internal use by [`swift!`].
#[must_use = "A Ref MUST be sent over to the Swift side"]
#[repr(transparent)]
pub struct SwiftRef<'a, T: SwiftObject>(&'a SRObjectImpl<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

@@ -0,0 +1,75 @@
use std::ffi::c_void;
use crate::{swift::SwiftObject, *};
/// Identifies a type as being a valid argument in a Swift function.
pub trait SwiftArg<'a> {
type ArgType;
/// Creates a swift-compatible version of the argument.
/// For primitives this just returns `self`,
/// but for [`SwiftObject`] types it wraps them in [`SwiftRef`].
///
/// This function is called within the [`swift!`] macro.
///
/// # Safety
///
/// Creating a [`SwiftRef`] is inherently unsafe,
/// but is reliable if using the [`swift!`] macro,
/// so it is not advised to call this function manually.
unsafe fn as_arg(&'a self) -> Self::ArgType;
}
macro_rules! primitive_impl {
($($t:ty),+) => {
$(impl<'a> SwiftArg<'a> for $t {
type ArgType = $t;
unsafe fn as_arg(&'a self) -> Self::ArgType {
*self
}
})+
};
}
primitive_impl!(
Bool,
Int,
Int8,
Int16,
Int32,
Int64,
UInt,
UInt8,
UInt16,
UInt32,
UInt64,
Float32,
Float64,
*const c_void,
*mut c_void,
*const u8,
()
);
macro_rules! ref_impl {
($($t:ident $(<$($gen:ident),+>)?),+) => {
$(impl<'a $($(, $gen: 'a),+)?> SwiftArg<'a> for $t$(<$($gen),+>)? {
type ArgType = SwiftRef<'a, $t$(<$($gen),+>)?>;
unsafe fn as_arg(&'a self) -> Self::ArgType {
self.swift_ref()
}
})+
};
}
ref_impl!(SRObject<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

@@ -0,0 +1,55 @@
use crate::{swift::SwiftObject, *};
use std::ffi::c_void;
/// Identifies a type as being a valid return type from a Swift function.
/// For types that are objects which need extra retains,
/// the [`retain`](SwiftRet::retain) function will be re-implemented.
pub trait SwiftRet {
/// Adds a retain to the value if possible
///
/// # Safety
/// Just don't use this.
/// Let [`swift!`] handle it.
unsafe fn retain(&self) {}
}
macro_rules! primitive_impl {
($($t:ty),+) => {
$(impl SwiftRet for $t {
})+
};
}
primitive_impl!(
Bool,
Int,
Int8,
Int16,
Int32,
Int64,
UInt,
UInt8,
UInt16,
UInt32,
UInt64,
Float32,
Float64,
*const c_void,
*mut c_void,
*const u8,
()
);
impl<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

@@ -0,0 +1,20 @@
//! Build script for swift-rs that is a no-op for normal builds, but can be enabled
//! to include test swift library based on env var `TEST_SWIFT_RS=true` with the
//! `build` feature being enabled.
#[cfg(feature = "build")]
mod build;
fn main() {
println!("cargo:rerun-if-env-changed=TEST_SWIFT_RS");
#[cfg(feature = "build")]
if std::env::var("TEST_SWIFT_RS").unwrap_or_else(|_| "false".into()) == "true" {
use build::SwiftLinker;
SwiftLinker::new("10.15")
.with_ios("11")
.with_package("test-swift", "tests/swift-pkg")
.link();
}
}

View File

@@ -0,0 +1,110 @@
use std::{ops::Deref, ptr::NonNull};
use crate::swift::SwiftObject;
use super::SRObject;
/// Wrapper of [`SRArray`] exclusively for arrays of objects.
/// Equivalent to `SRObjectArray` in Swift.
// SRArray is wrapped in SRObject since the Swift implementation extends NSObject
pub type SRObjectArray<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

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

@@ -0,0 +1,11 @@
mod array;
mod data;
mod object;
mod scalars;
mod string;
pub use array::*;
pub use data::*;
pub use object::*;
pub use scalars::*;
pub use string::*;

View File

@@ -0,0 +1,75 @@
use crate::swift::{self, SwiftObject};
use std::{ffi::c_void, ops::Deref, ptr::NonNull};
#[doc(hidden)]
#[repr(C)]
pub struct SRObjectImpl<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

@@ -0,0 +1,34 @@
/// Swift's [`Bool`](https://developer.apple.com/documentation/swift/bool) type
pub type Bool = bool;
/// Swift's [`Int`](https://developer.apple.com/documentation/swift/int) type
pub type Int = isize;
/// Swift's [`Int8`](https://developer.apple.com/documentation/swift/int8) type
pub type Int8 = i8;
/// Swift's [`Int16`](https://developer.apple.com/documentation/swift/int16) type
pub type Int16 = i16;
/// Swift's [`Int32`](https://developer.apple.com/documentation/swift/int32) type
pub type Int32 = i32;
/// Swift's [`Int64`](https://developer.apple.com/documentation/swift/int64) type
pub type Int64 = i64;
/// Swift's [`UInt`](https://developer.apple.com/documentation/swift/uint) type
pub type UInt = usize;
/// Swift's [`UInt8`](https://developer.apple.com/documentation/swift/uint8) type
pub type UInt8 = u8;
/// Swift's [`UInt16`](https://developer.apple.com/documentation/swift/uint16) type
pub type UInt16 = u16;
/// Swift's [`UInt32`](https://developer.apple.com/documentation/swift/uint32) type
pub type UInt32 = u32;
/// Swift's [`UInt64`](https://developer.apple.com/documentation/swift/uint64) type
pub type UInt64 = u64;
/// Swift's [`Float`](https://developer.apple.com/documentation/swift/float) type
pub type Float = f32;
/// Swift's [`Double`](https://developer.apple.com/documentation/swift/double) type
pub type Double = f64;
/// Swift's [`Float32`](https://developer.apple.com/documentation/swift/float32) type
pub type Float32 = f32;
/// Swift's [`Float64`](https://developer.apple.com/documentation/swift/float64) type
pub type Float64 = f64;

View File

@@ -0,0 +1,84 @@
use std::{
fmt::{Display, Error, Formatter},
ops::Deref,
};
use crate::{
swift::{self, SwiftObject},
Int, SRData, SRObject,
};
/// String type that can be shared between Swift and Rust.
///
/// ```rust
/// use swift_rs::{swift, SRString};
///
/// swift!(fn get_greeting(name: &SRString) -> SRString);
///
/// let greeting = unsafe { get_greeting(&"Brendan".into()) };
///
/// assert_eq!(greeting.as_str(), "Hello Brendan!");
/// ```
/// [_corresponding Swift code_](https://github.com/Brendonovich/swift-rs/blob/07269e511f1afb71e2fcfa89ca5d7338bceb20e8/tests/swift-pkg/doctests.swift#L56)
#[repr(transparent)]
pub struct SRString(SRData);
impl SRString {
pub fn as_str(&self) -> &str {
unsafe { std::str::from_utf8_unchecked(&self.0) }
}
}
impl SwiftObject for SRString {
type Shape = <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()))
}
}