clean rust

This commit is contained in:
EthanShoeDev
2025-09-13 12:34:41 -04:00
parent 6c972c8f13
commit b86371297b
71 changed files with 14 additions and 2993 deletions

View File

@@ -0,0 +1,83 @@
# Created by https://www.toptal.com/developers/gitignore/api/rust,xcode,android
# Edit at https://www.toptal.com/developers/gitignore?templates=rust,xcode,android
### Android ###
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Log/OS Files
*.log
# Android Studio generated files and folders
captures/
.externalNativeBuild/
.cxx/
*.apk
output.json
# IntelliJ
*.iml
.idea/
misc.xml
deploymentTargetDropDown.xml
render.experimental.xml
# Keystore files
*.jks
*.keystore
# Google Services (e.g. APIs or Firebase)
google-services.json
# Android Profiling
*.hprof
### Android Patch ###
gen-external-apklibs
# Replacement of .externalNativeBuild directories introduced
# with Android Studio 3.5.
### Rust ###
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
### Xcode ###
## User settings
xcuserdata/
## Xcode 8 and earlier
*.xcscmblueprint
*.xccheckout
### Xcode Patch ###
*.xcodeproj/*
!*.xcodeproj/project.pbxproj
!*.xcodeproj/xcshareddata/
!*.xcodeproj/project.xcworkspace/
!*.xcworkspace/contents.xcworkspacedata
/*.gcno
**/xcshareddata/WorkspaceSettings.xcsettings
# End of https://www.toptal.com/developers/gitignore/api/rust,xcode,android
# Swift Package Manager
.build/
# macOS Ignores
.DS_Store
# Android (cargo-ndk outputs; they end up in the Android source tree)
*.so

View File

@@ -0,0 +1,565 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "anstyle"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anyhow"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13"
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "basic-toml"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8"
dependencies = [
"serde",
]
[[package]]
name = "bytes"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
[[package]]
name = "camino"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3"
dependencies = [
"serde",
]
[[package]]
name = "cargo-platform"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc"
dependencies = [
"serde",
]
[[package]]
name = "cargo_metadata"
version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a"
dependencies = [
"camino",
"cargo-platform",
"semver",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "clap"
version = "4.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
dependencies = [
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
[[package]]
name = "fs-err"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41"
dependencies = [
"autocfg",
]
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "goblin"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47"
dependencies = [
"log",
"plain",
"scroll",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[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 = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "once_cell"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "plain"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
[[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 = "rinja"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dc4940d00595430b3d7d5a01f6222b5e5b51395d1120bdb28d854bb8abb17a5"
dependencies = [
"itoa",
"rinja_derive",
]
[[package]]
name = "rinja_derive"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d9ed0146aef6e2825f1b1515f074510549efba38d71f4554eec32eb36ba18b"
dependencies = [
"basic-toml",
"memchr",
"mime",
"mime_guess",
"proc-macro2",
"quote",
"rinja_parser",
"rustc-hash",
"serde",
"syn",
]
[[package]]
name = "rinja_parser"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93f9a866e2e00a7a1fb27e46e9e324a6f7c0e7edc4543cae1d38f4e4a100c610"
dependencies = [
"memchr",
"nom",
"serde",
]
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "scroll"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6"
dependencies = [
"scroll_derive",
]
[[package]]
name = "scroll_derive"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f81c2fde025af7e69b1d1420531c8a8811ca898919db177141a85313b1cb932"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "semver"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
dependencies = [
"serde",
]
[[package]]
name = "serde"
version = "1.0.214"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.214"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "siphasher"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]]
name = "smawk"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[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 = "textwrap"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
dependencies = [
"smawk",
]
[[package]]
name = "thiserror"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3c6efbfc763e64eb85c11c25320f0737cb7364c4b6336db90aa9ebe27a0bbd"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b607164372e89797d78b8e23a6d67d5d1038c1c65efd52e1389ef8b77caba2a6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "toml"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
[[package]]
name = "unicase"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df"
[[package]]
name = "unicode-ident"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "uniffi"
version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe34585ac0275accf6c284d0080cc2840f3898c551cda869ec291b5a4218712c"
dependencies = [
"anyhow",
"camino",
"cargo_metadata",
"clap",
"uniffi_bindgen",
"uniffi_build",
"uniffi_core",
"uniffi_macros",
]
[[package]]
name = "uniffi-bindgen"
version = "0.1.0"
dependencies = [
"uniffi",
]
[[package]]
name = "uniffi-bindgen-swift"
version = "0.1.0"
dependencies = [
"uniffi",
]
[[package]]
name = "uniffi-russh"
version = "0.1.0"
dependencies = [
"thiserror",
"uniffi",
]
[[package]]
name = "uniffi_bindgen"
version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a792af1424cc8b3c43b44c1a6cb7935ed1fbe5584a74f70e8bab9799740266d"
dependencies = [
"anyhow",
"camino",
"cargo_metadata",
"fs-err",
"glob",
"goblin",
"heck",
"once_cell",
"paste",
"rinja",
"serde",
"textwrap",
"toml",
"uniffi_meta",
"uniffi_udl",
]
[[package]]
name = "uniffi_build"
version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00c4138211f2ae951018fcce6a978e1fcd1a47c3fd0bc0d5472a520520060db1"
dependencies = [
"anyhow",
"camino",
"uniffi_bindgen",
]
[[package]]
name = "uniffi_core"
version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c18baace68a52666d33d12d73ca335ecf27a302202cefb53b1f974512bb72417"
dependencies = [
"anyhow",
"bytes",
"once_cell",
"static_assertions",
]
[[package]]
name = "uniffi_internal_macros"
version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9902d4ed16c65e6c0222241024dd0bfeed07ea3deb7c470eb175e5f5ef406cd"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "uniffi_macros"
version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d82c82ef945c51082d8763635334b994e63e77650f09d0fae6d28dd08b1de83"
dependencies = [
"camino",
"fs-err",
"once_cell",
"proc-macro2",
"quote",
"serde",
"syn",
"toml",
"uniffi_meta",
]
[[package]]
name = "uniffi_meta"
version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d6027b971c2aa86350dd180aee9819729c7b99bacd381534511ff29d2c09cea"
dependencies = [
"anyhow",
"siphasher",
"uniffi_internal_macros",
]
[[package]]
name = "uniffi_udl"
version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52300b7a4ab02dc159a038a13d5bfe27aefbad300d91b0b501b3dda094c1e0a2"
dependencies = [
"anyhow",
"textwrap",
"uniffi_meta",
"weedle2",
]
[[package]]
name = "weedle2"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e"
dependencies = [
"nom",
]

View File

@@ -0,0 +1,11 @@
[workspace]
members = [
"uniffi-russh",
"uniffi-bindgen",
"uniffi-bindgen-swift",
]
resolver = "2"
[workspace.dependencies]
uniffi = "0.29"

View File

@@ -0,0 +1,75 @@
#!/usr/bin/env zsh
set -e
set -u
# NOTE: You MUST run this every time you make changes to the core. Unfortunately, calling this from Xcode directly
# does not work so well.
# In release mode, we create a ZIP archive of the xcframework and update Package.swift with the computed checksum.
# This is only needed when cutting a new release, not for local development.
release=false
for arg in "$@"
do
case $arg in
--release)
release=true
shift # Remove --release from processing
;;
*)
shift # Ignore other argument from processing
;;
esac
done
# Potential optimizations for the future:
#
# * Only build one simulator arch for local development (we build both since many still use Intel Macs)
# * Option to do debug builds instead for local development
fat_simulator_lib_dir="target/ios-simulator-fat/release"
generate_ffi() {
echo "Generating framework module mapping and FFI bindings"
# NOTE: Convention requires the modulemap be named module.modulemap
cargo run -p uniffi-bindgen-swift -- target/aarch64-apple-ios/release/lib$1.a target/uniffi-xcframework-staging --swift-sources --headers --modulemap --module-name $1FFI --modulemap-filename module.modulemap
mkdir -p ../apple/Sources/UniFFI/
mv target/uniffi-xcframework-staging/*.swift ../apple/Sources/UniFFI/
mv target/uniffi-xcframework-staging/module.modulemap target/uniffi-xcframework-staging/module.modulemap
}
create_fat_simulator_lib() {
echo "Creating a fat library for x86_64 and aarch64 simulators"
mkdir -p $fat_simulator_lib_dir
lipo -create target/x86_64-apple-ios/release/lib$1.a target/aarch64-apple-ios-sim/release/lib$1.a -output $fat_simulator_lib_dir/lib$1.a
}
build_xcframework() {
# Builds an XCFramework
echo "Generating XCFramework"
rm -rf target/ios # Delete the output folder so we can regenerate it
xcodebuild -create-xcframework \
-library target/aarch64-apple-ios/release/lib$1.a -headers target/uniffi-xcframework-staging \
-library target/ios-simulator-fat/release/lib$1.a -headers target/uniffi-xcframework-staging \
-output target/ios/lib$1-rs.xcframework
if $release; then
echo "Building xcframework archive"
ditto -c -k --sequesterRsrc --keepParent target/ios/lib$1-rs.xcframework target/ios/lib$1-rs.xcframework.zip
checksum=$(swift package compute-checksum target/ios/lib$1-rs.xcframework.zip)
version=$(cargo metadata --format-version 1 | jq -r --arg pkg_name "$1" '.packages[] | select(.name==$pkg_name) .version')
sed -i "" -E "s/(let releaseTag = \")[^\"]+(\")/\1$version\2/g" ../Package.swift
sed -i "" -E "s/(let releaseChecksum = \")[^\"]+(\")/\1$checksum\2/g" ../Package.swift
fi
}
basename=uniffi-russh
cargo build -p $basename --lib --release --target x86_64-apple-ios
cargo build -p $basename --lib --release --target aarch64-apple-ios-sim
cargo build -p $basename --lib --release --target aarch64-apple-ios
generate_ffi $basename
create_fat_simulator_lib $basename
build_xcframework $basename

View File

@@ -0,0 +1,28 @@
# See https://rust-lang.github.io/rustup/overrides.html for details on how this file works
# and how you can override the choices made herein.
#
# Note that we UniFFI also sets their own toolchain (see https://github.com/mozilla/uniffi-rs/blob/main/rust-toolchain.toml).
# We will attempt to track stable unless we find that this breaks something.
# The iOS targets are easy and well-known, but the Android targets take a bit more work to deduce.
# We have drawn our list from https://github.com/mozilla/rust-android-gradle.
[toolchain]
channel = "stable"
targets = [
# iOS
"aarch64-apple-ios",
"x86_64-apple-ios",
"aarch64-apple-ios-sim",
# Android
"armv7-linux-androideabi",
"i686-linux-android",
"aarch64-linux-android",
"x86_64-linux-android",
"x86_64-unknown-linux-gnu",
"x86_64-apple-darwin",
"aarch64-apple-darwin",
"x86_64-pc-windows-gnu",
"x86_64-pc-windows-msvc",
]
components = ["clippy", "rustfmt"]

View File

@@ -0,0 +1,13 @@
[package]
name = "uniffi-bindgen-swift"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
uniffi = { workspace = true, features = ["cli"] }
[[bin]]
name = "uniffi-bindgen-swift"
path = "src/main.rs"

View File

@@ -0,0 +1,3 @@
fn main() {
uniffi::uniffi_bindgen_swift();
}

View File

@@ -0,0 +1,14 @@
[package]
name = "uniffi-bindgen"
version = "0.1.0"
edition = "2021"
publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
uniffi = { workspace = true, features = ["cli"] }
[[bin]]
name = "uniffi-bindgen"
path = "src/main.rs"

View File

@@ -0,0 +1,3 @@
fn main() {
uniffi::uniffi_bindgen_main()
}

View File

@@ -0,0 +1,16 @@
[package]
name = "uniffi-russh"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
uniffi.workspace = true
thiserror = "1.0.64"
[build-dependencies]
uniffi = { workspace = true, features = ["build"] }
[lib]
crate-type = ["cdylib", "staticlib", "lib"]

View File

@@ -0,0 +1,212 @@
use std::sync::Arc;
// You must call this once
uniffi::setup_scaffolding!();
// What follows is an intentionally ridiculous whirlwind tour of how you'd expose a complex API to UniFFI.
#[derive(Debug, PartialEq, uniffi::Enum, Default)]
pub enum ComputationState {
/// Initial state with no value computed
#[default]
Init,
Computed {
result: ComputationResult,
},
}
#[derive(Copy, Clone, Debug, PartialEq, uniffi::Record)]
pub struct ComputationResult {
pub value: i64,
}
#[derive(Debug, PartialEq, thiserror::Error, uniffi::Error)]
pub enum ComputationError {
#[error("Division by zero is not allowed.")]
DivisionByZero,
#[error("Result overflowed the numeric type bounds.")]
Overflow,
#[error("There is no existing computation state, so you cannot perform this operation.")]
IllegalComputationWithInitState,
}
/// A binary operator that performs some mathematical operation with two numbers.
#[uniffi::export(with_foreign)]
pub trait BinaryOperator: Send + Sync {
fn perform(&self, lhs: i64, rhs: i64) -> Result<i64, ComputationError>;
}
/// A somewhat silly demonstration of functional core/imperative shell in the form of a calculator with arbitrary operators.
///
/// Operations return a new calculator with updated internal state reflecting the computation.
#[derive(PartialEq, Debug, Default, uniffi::Object)]
pub struct Calculator {
state: ComputationState,
}
#[uniffi::export]
impl Calculator {
#[uniffi::constructor]
pub fn new() -> Self {
Self::default()
}
pub fn last_result(&self) -> Option<ComputationResult> {
match self.state {
ComputationState::Init => None,
ComputationState::Computed { result } => Some(result),
}
}
/// Performs a calculation using the supplied binary operator and operands.
pub fn calculate(
&self,
op: Arc<dyn BinaryOperator>,
lhs: i64,
rhs: i64,
) -> Result<Calculator, ComputationError> {
let value = op.perform(lhs, rhs)?;
Ok(Calculator {
state: ComputationState::Computed {
result: ComputationResult { value },
},
})
}
/// Performs a calculation using the supplied binary operator, the last computation result, and the supplied operand.
///
/// The supplied operand will be the right-hand side in the mathematical operation.
pub fn calculate_more(
&self,
op: Arc<dyn BinaryOperator>,
rhs: i64,
) -> Result<Calculator, ComputationError> {
let ComputationState::Computed { result } = &self.state else {
return Err(ComputationError::IllegalComputationWithInitState);
};
let value = op.perform(result.value, rhs)?;
Ok(Calculator {
state: ComputationState::Computed {
result: ComputationResult { value },
},
})
}
}
#[derive(uniffi::Object)]
struct SafeAddition {}
// Makes it easy to construct from foreign code
#[uniffi::export]
impl SafeAddition {
#[uniffi::constructor]
fn new() -> Self {
SafeAddition {}
}
}
#[uniffi::export]
impl BinaryOperator for SafeAddition {
fn perform(&self, lhs: i64, rhs: i64) -> Result<i64, ComputationError> {
lhs.checked_add(rhs).ok_or(ComputationError::Overflow)
}
}
#[derive(uniffi::Object)]
struct SafeDivision {}
// Makes it easy to construct from foreign code
#[uniffi::export]
impl SafeDivision {
#[uniffi::constructor]
fn new() -> Self {
SafeDivision {}
}
}
#[uniffi::export]
impl BinaryOperator for SafeDivision {
fn perform(&self, lhs: i64, rhs: i64) -> Result<i64, ComputationError> {
if rhs == 0 {
Err(ComputationError::DivisionByZero)
} else {
lhs.checked_div(rhs).ok_or(ComputationError::Overflow)
}
}
}
// Helpers that only exist because the concrete objects above DO NOT have the requisite protocol conformances
// stated in the glue code. It's easy to extend classes in Swift, but you can't just declare a conformance in Kotlin.
// So, to keep things easy, we just do this as a compromise.
#[uniffi::export]
fn safe_addition_operator() -> Arc<dyn BinaryOperator> {
Arc::new(SafeAddition::new())
}
#[uniffi::export]
fn safe_division_operator() -> Arc<dyn BinaryOperator> {
Arc::new(SafeDivision::new())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn addition() {
let calc = Calculator::new();
let op = Arc::new(SafeAddition {});
let calc = calc
.calculate(op.clone(), 2, 2)
.expect("Something went wrong");
assert_eq!(calc.last_result().unwrap().value, 4);
assert_eq!(
calc.calculate_more(op.clone(), i64::MAX),
Err(ComputationError::Overflow)
);
assert_eq!(
calc.calculate_more(op, 8)
.unwrap()
.last_result()
.unwrap()
.value,
12
);
}
#[test]
fn division() {
let calc = Calculator::new();
let op = Arc::new(SafeDivision {});
let calc = calc
.calculate(op.clone(), 2, 2)
.expect("Something went wrong");
assert_eq!(calc.last_result().unwrap().value, 1);
assert_eq!(
calc.calculate_more(op.clone(), 0),
Err(ComputationError::DivisionByZero)
);
assert_eq!(
calc.calculate(op, i64::MIN, -1),
Err(ComputationError::Overflow)
);
}
#[test]
fn compute_more_from_init_state() {
let calc = Calculator::new();
let op = Arc::new(SafeAddition {});
assert_eq!(
calc.calculate_more(op, 1),
Err(ComputationError::IllegalComputationWithInitState)
);
}
}