Skip to content

Commit

Permalink
Add support for Result<T, E> (#111)
Browse files Browse the repository at this point in the history
This commit adds suppoer for passing a `Result<T, E>` as an argument
from Rust -> Swift.

For example, the following is not possible:

```rust
use some_crate::SomeRustType;

#[swift_bridge::bridge]
mod ffi {
    extern "Swift" {
        fn swift_func_takes_callback(
            arg: Box<dyn FnOnce(Result<SomeRustType, String>)>,
        );
    }

    extern "Rust" {
        type SomeRustType;

        #[swift_bridge::init]
        fn new () -> SomeRustType;
    }
}
```
  • Loading branch information
chinedufn authored Sep 20, 2022
1 parent 2ad1757 commit 420ed79
Show file tree
Hide file tree
Showing 27 changed files with 792 additions and 54 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ In addition to allowing you to share your own custom structs, enums and classes
| *const T | UnsafePointer\<T> | |
| *mut T | UnsafeMutablePointer\<T> | |
| Option\<T> | Optional\<T> | |
| Result\<T> | | Not yet implemented |
| Result\<T, E> | RustResult\<T, E> | |
| Have a Rust standard library type in mind?<br /> Open an issue! | | |
| | Have a Swift standard library type in mind?<br /> Open an issue! | |
<!-- ANCHOR_END: built-in-types-table -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
221E16B42786233600F94AC0 /* ConditionalCompilationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 221E16B32786233600F94AC0 /* ConditionalCompilationTests.swift */; };
221E16B62786F9FF00F94AC0 /* OpaqueTypeAttributeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 221E16B52786F9FF00F94AC0 /* OpaqueTypeAttributeTests.swift */; };
22553324281DB5FC008A3121 /* GenericTests.rs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22553323281DB5FC008A3121 /* GenericTests.rs.swift */; };
225908FC28DA0E320080C737 /* ResultTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225908FB28DA0E320080C737 /* ResultTests.swift */; };
225908FE28DA0F9F0080C737 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225908FD28DA0F9F0080C737 /* Result.swift */; };
226F944B27BF79B400243D86 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 226F944A27BF79B400243D86 /* String.swift */; };
228FE5D52740DB6A00805D9E /* SwiftRustIntegrationTestRunnerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 228FE5D42740DB6A00805D9E /* SwiftRustIntegrationTestRunnerApp.swift */; };
228FE5D72740DB6A00805D9E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 228FE5D62740DB6A00805D9E /* ContentView.swift */; };
Expand Down Expand Up @@ -72,6 +74,8 @@
221E16B32786233600F94AC0 /* ConditionalCompilationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalCompilationTests.swift; sourceTree = "<group>"; };
221E16B52786F9FF00F94AC0 /* OpaqueTypeAttributeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpaqueTypeAttributeTests.swift; sourceTree = "<group>"; };
22553323281DB5FC008A3121 /* GenericTests.rs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericTests.rs.swift; sourceTree = "<group>"; };
225908FB28DA0E320080C737 /* ResultTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultTests.swift; sourceTree = "<group>"; };
225908FD28DA0F9F0080C737 /* Result.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = "<group>"; };
226F944A27BF79B400243D86 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
228FE5D12740DB6A00805D9E /* SwiftRustIntegrationTestRunner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftRustIntegrationTestRunner.app; sourceTree = BUILT_PRODUCTS_DIR; };
228FE5D42740DB6A00805D9E /* SwiftRustIntegrationTestRunnerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftRustIntegrationTestRunnerApp.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -166,6 +170,7 @@
228FE5D42740DB6A00805D9E /* SwiftRustIntegrationTestRunnerApp.swift */,
22EE4E0828B5388000FEC83C /* SwiftFnUsesOpaqueSwiftType.swift */,
22C0625228CE699D007A6F67 /* Callbacks.swift */,
225908FD28DA0F9F0080C737 /* Result.swift */,
);
path = SwiftRustIntegrationTestRunner;
sourceTree = "<group>";
Expand All @@ -190,6 +195,7 @@
228FE61127428A8D00805D9E /* OpaqueSwiftStructTests.swift */,
221E16B52786F9FF00F94AC0 /* OpaqueTypeAttributeTests.swift */,
22043294274ADA7A00BAE645 /* OptionTests.swift */,
225908FB28DA0E320080C737 /* ResultTests.swift */,
220432A6274C953E00BAE645 /* PointerTests.swift */,
220432EB27530AFC00BAE645 /* RustFnUsesOpaqueSwiftTypeTests.swift */,
2202BC0727B2DD1700D43CC4 /* SharedEnumTests.swift */,
Expand Down Expand Up @@ -363,6 +369,7 @@
220432EA2753092C00BAE645 /* RustFnUsesOpaqueSwiftType.swift in Sources */,
22FD1C542753CB2A00F64281 /* SwiftFnUsesOpaqueRustType.swift in Sources */,
220432A9274D31DC00BAE645 /* Pointer.swift in Sources */,
225908FE28DA0F9F0080C737 /* Result.swift in Sources */,
228FE5D72740DB6A00805D9E /* ContentView.swift in Sources */,
228FE64E2749C3D700805D9E /* Boolean.swift in Sources */,
228FE64627480E1D00805D9E /* SwiftBridgeCore.swift in Sources */,
Expand All @@ -385,6 +392,7 @@
220432AF274E7BF800BAE645 /* SharedStructTests.swift in Sources */,
220432EC27530AFC00BAE645 /* RustFnUsesOpaqueSwiftTypeTests.swift in Sources */,
22553324281DB5FC008A3121 /* GenericTests.rs.swift in Sources */,
225908FC28DA0E320080C737 /* ResultTests.swift in Sources */,
228FE6502749C43100805D9E /* BooleanTests.swift in Sources */,
2202BC0827B2DD1700D43CC4 /* SharedEnumTests.swift in Sources */,
22BCAAB927A2607700686A21 /* FunctionAttributeIdentifiableTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//
// Result.swift
// SwiftRustIntegrationTestRunner
//
// Created by Frankie Nwafili on 9/20/22.
//

func swift_func_takes_callback_with_result_arg(
arg: (RustResult<CallbackTestOpaqueRustType, String>) -> ()
) {
arg(.Ok(CallbackTestOpaqueRustType(555)))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// ResultTests.swift
// SwiftRustIntegrationTestRunnerTests
//
// Created by Frankie Nwafili on 9/20/22.
//

import XCTest
@testable import SwiftRustIntegrationTestRunner

class ResultTests: XCTestCase {
/// Verify that we can pass a Result<String, String> from Swift -> Rust
func testSwiftCallRustResultString() throws {
rust_func_takes_result_string(.Ok("Success Message"))
rust_func_takes_result_string(.Err("Error Message"))
}

/// Verify that we can pass a Result<OpaqueRust, OpaqueRust> from Swift -> Rust
func testSwiftCallRustResultOpaqueRust() throws {
rust_func_takes_result_opaque_rust(
.Ok(ResultTestOpaqueRustType(111))
)
rust_func_takes_result_opaque_rust(
.Err(ResultTestOpaqueRustType(222))
)
}
}
5 changes: 3 additions & 2 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@
- [Conditional Compilation](./bridge-module/conditional-compilation/README.md)

- [Built In Types](./built-in/README.md)
- [Option<T> <---> Optional<T>](./built-in/option/README.md)
- [Vec<T> <---> RustVec<T>](./built-in/vec/README.md)
- [String <---> String](./built-in/string/README.md)
- [&str <---> RustStr](./built-in/str/README.md)
- [Vec<T> <---> RustVec<T>](./built-in/vec/README.md)
- [Option<T> <---> Optional<T>](./built-in/option/README.md)
- [Result<T, E> <---> RustResult<T, E>](./built-in/result/README.md)
- [Box<dyn FnOnce(A, B) -> C>](./built-in/boxed-functions/README.md)

- [Safety](./safety/README.md)
Expand Down
27 changes: 27 additions & 0 deletions book/src/built-in/result/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Result

Rust's `Result` is seen on the Swift side as a `RustResult`.

## Example

```rust,no_run
// Rust
#[swift_bridge::bridge]
mod ffi {
extern "Swift" {
fn run(
arg: Box<dyn FnOnce(Result<SomeRustType, String>)>
);
}
extern "Rust" {
type SomeRustType;
}
```

```swift
func run(arg: (RustResult<SomeRustType, String>) -> ()) {
arg(.Err("Something went wrong"))
}
```
6 changes: 6 additions & 0 deletions crates/swift-bridge-build/src/generate_core.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::generate_core::boxed_fn_support::{
C_CALLBACK_SUPPORT_NO_ARGS_NO_RETURN, SWIFT_CALLBACK_SUPPORT_NO_ARGS_NO_RETURN,
};
use crate::generate_core::result_support::{C_RESULT_SUPPORT, SWIFT_RUST_RESULT};
use std::path::{Path, PathBuf};

const RUST_STRING_SWIFT: &'static str = include_str!("./generate_core/rust_string.swift");
Expand All @@ -10,6 +11,7 @@ const STRING_SWIFT: &'static str = include_str!("./generate_core/string.swift");
const RUST_VEC_SWIFT: &'static str = include_str!("./generate_core/rust_vec.swift");

mod boxed_fn_support;
mod result_support;

pub(super) fn write_core_swift_and_c(out_dir: &Path) {
let core_swift_out = out_dir.join("SwiftBridgeCore.swift");
Expand All @@ -18,6 +20,8 @@ pub(super) fn write_core_swift_and_c(out_dir: &Path) {
swift += &RUST_STRING_SWIFT;
swift += "\n";
swift += &SWIFT_CALLBACK_SUPPORT_NO_ARGS_NO_RETURN;
swift += "\n";
swift += &SWIFT_RUST_RESULT;

std::fs::write(core_swift_out, swift).unwrap();

Expand All @@ -27,6 +31,8 @@ pub(super) fn write_core_swift_and_c(out_dir: &Path) {
c_header += &RUST_STRING_C;
c_header += "\n";
c_header += &C_CALLBACK_SUPPORT_NO_ARGS_NO_RETURN;
c_header += "\n";
c_header += &C_RESULT_SUPPORT;

std::fs::write(core_c_header_out, c_header).unwrap();
}
Expand Down
40 changes: 40 additions & 0 deletions crates/swift-bridge-build/src/generate_core/result_support.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
pub const SWIFT_RUST_RESULT: &'static str = r#"
public enum RustResult<T, E> {
case Ok(T)
case Err(E)
}
extension RustResult {
func ok() -> T? {
switch self {
case .Ok(let ok):
return ok
case .Err(_):
return nil
}
}
func err() -> E? {
switch self {
case .Ok(_):
return nil
case .Err(let err):
return err
}
}
func toResult() -> Result<T, E>
where E: Error {
switch self {
case .Ok(let ok):
return .success(ok)
case .Err(let err):
return .failure(err)
}
}
}
"#;

pub const C_RESULT_SUPPORT: &'static str = r#"
struct __private__ResultPtrAndPtr { bool is_ok; void* ok_or_err; };
"#;
42 changes: 41 additions & 1 deletion crates/swift-bridge-ir/src/bridged_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::ops::Deref;
use std::str::FromStr;

use crate::bridged_type::boxed_fn::BuiltInBoxedFnOnce;
use crate::bridged_type::bridged_result::BuiltInResult;
use proc_macro2::{Ident, Span, TokenStream};
use quote::ToTokens;
use quote::{quote, quote_spanned};
Expand All @@ -17,6 +18,7 @@ pub(crate) use self::shared_struct::{SharedStruct, StructFields, StructSwiftRepr

pub(crate) mod boxed_fn;
mod bridged_option;
mod bridged_result;
mod shared_enum;
pub(crate) mod shared_struct;

Expand Down Expand Up @@ -62,6 +64,7 @@ pub(crate) enum StdLibType {
Vec(BuiltInVec),
BoxedFnOnce(BuiltInBoxedFnOnce),
Option(BridgedOption),
Result(BuiltInResult),
}

/// TODO: Add this to `OpaqueForeignType`
Expand Down Expand Up @@ -405,6 +408,10 @@ impl BridgedType {
return Some(BridgedType::StdLib(StdLibType::Option(BridgedOption {
ty: Box::new(inner),
})));
} else if string.starts_with("Result < ") {
return Some(BridgedType::StdLib(StdLibType::Result(
BuiltInResult::from_str_tokens(&string, types)?,
)));
} else if string.starts_with("Box < dyn FnOnce") {
return Some(BridgedType::StdLib(StdLibType::BoxedFnOnce(
BuiltInBoxedFnOnce::from_str_tokens(&string, types)?,
Expand All @@ -426,6 +433,7 @@ impl BridgedType {
"f64" => BridgedType::StdLib(StdLibType::F64),
"String" => BridgedType::StdLib(StdLibType::String),
"bool" => BridgedType::StdLib(StdLibType::Bool),
"()" => BridgedType::StdLib(StdLibType::Null),
_ => {
let bridged_type = types.get(string)?;
let bridged_type = bridged_type.to_bridged_type(false, false);
Expand Down Expand Up @@ -487,6 +495,7 @@ impl BridgedType {
let ty = opt.ty.to_rust_type_path();
quote! { Option<#ty> }
}
StdLibType::Result(result) => result.to_rust_type_path(),
StdLibType::BoxedFnOnce(fn_once) => fn_once.to_rust_type_path(),
}
}
Expand Down Expand Up @@ -636,6 +645,9 @@ impl BridgedType {
StdLibType::Option(_) => {
todo!("Option<Option<T>> is not yet supported")
}
StdLibType::Result(_) => {
todo!("Option<Result<T, E>> is not yet supported")
}
StdLibType::BoxedFnOnce(_) => {
todo!("Support Box<dyn FnOnce(A, B) -> C>")
}
Expand Down Expand Up @@ -666,6 +678,7 @@ impl BridgedType {
}
}
},
StdLibType::Result(result) => result.to_ffi_compatible_rust_type(swift_bridge_path),
StdLibType::BoxedFnOnce(fn_once) => fn_once.to_ffi_compatible_rust_type(),
},
BridgedType::Foreign(CustomBridgedType::Shared(SharedType::Struct(shared_struct))) => {
Expand Down Expand Up @@ -823,6 +836,7 @@ impl BridgedType {
unimplemented!()
}
},
StdLibType::Result(result) => result.to_swift_type(type_pos, types),
StdLibType::BoxedFnOnce(boxed_fn) => boxed_fn.to_swift_type().to_string(),
},
BridgedType::Foreign(CustomBridgedType::Shared(SharedType::Struct(shared_struct))) => {
Expand Down Expand Up @@ -952,6 +966,7 @@ impl BridgedType {
StdLibType::String => "void*".to_string(),
StdLibType::Vec(_) => "void*".to_string(),
StdLibType::Option(opt) => opt.to_c(),
StdLibType::Result(result) => result.to_c().to_string(),
StdLibType::BoxedFnOnce(_) => "void*".to_string(),
},
BridgedType::Foreign(CustomBridgedType::Shared(SharedType::Struct(shared_struct))) => {
Expand Down Expand Up @@ -1073,6 +1088,9 @@ impl BridgedType {
StdLibType::Option(opt) => {
opt.convert_rust_value_to_ffi_value(expression, swift_bridge_path)
}
StdLibType::Result(_) => {
todo!("Result<T, E> is not yet supported")
}
StdLibType::BoxedFnOnce(fn_once) => {
fn_once.convert_rust_value_to_ffi_compatible_value(expression)
}
Expand Down Expand Up @@ -1129,7 +1147,13 @@ impl BridgedType {
// RustStr -> &str
// *mut RustString -> String
// FfiSlice<u8> -> &[u8]
pub fn convert_ffi_value_to_rust_value(&self, value: &TokenStream, span: Span) -> TokenStream {
pub fn convert_ffi_value_to_rust_value(
&self,
value: &TokenStream,
span: Span,
swift_bridge_path: &Path,
types: &TypeDeclarations,
) -> TokenStream {
match self {
BridgedType::StdLib(stdlib_type) => match stdlib_type {
StdLibType::Null
Expand Down Expand Up @@ -1170,6 +1194,9 @@ impl BridgedType {
StdLibType::Option(bridged_option) => {
bridged_option.convert_ffi_value_to_rust_value(value)
}
StdLibType::Result(result) => {
result.convert_ffi_value_to_rust_value(value, span, swift_bridge_path, types)
}
StdLibType::BoxedFnOnce(_) => {
todo!("Support Box<dyn FnOnce(A, B) -> C>")
}
Expand Down Expand Up @@ -1305,6 +1332,9 @@ impl BridgedType {
format!("RustVec(ptr: {})", value)
}
StdLibType::Option(opt) => opt.convert_ffi_expression_to_swift(value),
StdLibType::Result(_) => {
todo!("Result<T, E> is not yet supported")
}
StdLibType::BoxedFnOnce(fn_once) => {
fn_once.convert_ffi_value_to_swift_value(type_pos)
}
Expand Down Expand Up @@ -1427,6 +1457,9 @@ impl BridgedType {
StdLibType::Option(option) => {
option.convert_swift_expression_to_ffi_compatible(value, type_pos)
}
StdLibType::Result(result) => {
result.convert_swift_expression_to_ffi_compatible(value, type_pos)
}
StdLibType::BoxedFnOnce(_) => {
todo!("Support Box<dyn FnOnce(A, B) -> C>")
}
Expand Down Expand Up @@ -1602,6 +1635,9 @@ impl BridgedType {
StdLibType::Option(_) => {
todo!("Support nested Option<Option<T>>")
}
StdLibType::Result(_) => {
todo!("Result<T, E> is not yet supported")
}
StdLibType::BoxedFnOnce(_) => {
todo!("Support Box<dyn FnOnce(A, B) -> C>")
}
Expand Down Expand Up @@ -1643,6 +1679,10 @@ impl BridgedType {
StdLibType::String => true,
StdLibType::Vec(inner) => inner.ty.contains_owned_string_recursive(),
StdLibType::Option(inner) => inner.ty.contains_owned_string_recursive(),
StdLibType::Result(inner) => {
inner.ok_ty.contains_owned_string_recursive()
|| inner.err_ty.contains_owned_string_recursive()
}
_ => false,
},
BridgedType::Foreign(CustomBridgedType::Shared(SharedType::Struct(_shared_struct))) => {
Expand Down
Loading

0 comments on commit 420ed79

Please sign in to comment.