Skip to content

Commit

Permalink
Support Rust returning -> Result<_, String> (#296)
Browse files Browse the repository at this point in the history
Support Rust returning `-> Result<_, String>`, e.g.:

```rust
#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        fn do_fallible_work() -> Result<(), String>;
    }
}
```

---

Incorporate the changes from #282 to implement swift's `Error` protocol for ruststring, & extend them with the tests requested in #295 (comment)
  • Loading branch information
brittlewis12 authored Oct 14, 2024
1 parent 0698b41 commit c45a38c
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,30 @@ class ResultTests: XCTestCase {
rust_func_takes_result_string(.Ok("Success Message"))
rust_func_takes_result_string(.Err("Error Message"))
}


/// Verify that we can return a Result<String, String> from Rust -> Swift.
///
/// The Err case evidences Swift’s `Error` protocol is implemented correctly
/// for `RustStringRef`, i.e. `extension RustStringRef: Error {}`
func testSwiftCallRustReturnsResultString() throws {
let resultOk = try! rust_func_returns_result_string(true)
XCTAssertEqual(resultOk.toString(), "Success Message")

do {
let _ = try rust_func_returns_result_string(false)
XCTFail("The function should have returned an error.")
} catch let error as RustString {
XCTAssertEqual(error.toString(), "Error Message")
}
}

/// Verify that we can pass a Result<OpaqueRust, OpaqueRust> from Swift -> Rust
func testSwiftCallRustResultOpaqueRust() throws {
let reflectedOk = try! rust_func_reflect_result_opaque_rust(
.Ok(ResultTestOpaqueRustType(111))
)
XCTAssertEqual(reflectedOk.val(), 111)

do {
let _ = try rust_func_reflect_result_opaque_rust(
.Err(ResultTestOpaqueRustType(222))
Expand All @@ -30,7 +46,7 @@ class ResultTests: XCTestCase {
XCTAssertEqual(error.val(), 222)
}
}

/// Verify that we can pass a Result<OpaqueSwift, OpaqueSwift> from Swift -> Rust
func testSwiftCallRustResultOpaqueSwift() throws {
rust_func_takes_result_opaque_swift(
Expand Down Expand Up @@ -64,7 +80,7 @@ class ResultTests: XCTestCase {
XCTAssertEqual(error.val(), 222)
}
}

/// Verify that we can receive a Result<OpaqueRust, TransparentEnum> from Rust
func testResultOpaqueRustTransparentEnum() throws {
XCTContext.runActivity(named: "Should return a ResultTestOpaqueRustType") {
Expand All @@ -75,7 +91,7 @@ class ResultTests: XCTestCase {
XCTFail()
}
}

XCTContext.runActivity(named: "Should throw an error") {
_ in
do {
Expand All @@ -95,7 +111,7 @@ class ResultTests: XCTestCase {
}
}
}

/// Verify that we can receive a Result<TransparentEnum, OpaqueRust> from Rust
func testResultTransparentEnumOpaqueRust() throws {
XCTContext.runActivity(named: "Should return a ResultTestOpaqueRustType") {
Expand All @@ -114,7 +130,7 @@ class ResultTests: XCTestCase {
XCTFail()
}
}

XCTContext.runActivity(named: "Should throw an error") {
_ in
do {
Expand All @@ -127,7 +143,7 @@ class ResultTests: XCTestCase {
}
}
}

/// Verify that we can receive a Result<(), TransparentEnum> from Rust
func testResultUnitTypeTransparentEnum() throws {
XCTContext.runActivity(named: "Should return a Unit type") {
Expand All @@ -138,7 +154,7 @@ class ResultTests: XCTestCase {
XCTFail()
}
}

XCTContext.runActivity(named: "Should throw an error") {
_ in
do {
Expand All @@ -158,7 +174,7 @@ class ResultTests: XCTestCase {
}
}
}

/// Verify that we can receive a Result<(primitive type, OpaqueRustType, String), TransparentEnum> from Rust
func testResultTupleTransparentEnum() throws {
XCTContext.runActivity(named: "Should return a tuple type") {
Expand All @@ -172,7 +188,7 @@ class ResultTests: XCTestCase {
XCTFail()
}
}

XCTContext.runActivity(named: "Should throw an error") {
_ in
do {
Expand Down Expand Up @@ -249,7 +265,7 @@ class ResultTests: XCTestCase {
XCTAssertEqual(UInt32(i), value.val())
}
}

/// Verify that we can use throwing initializers defined on the Rust side.
func testThrowingInitializers() throws {
XCTContext.runActivity(named: "Should fail") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ extension RustStringRef {
__swift_bridge__$RustString$trim(ptr)
}
}
/// exercised in SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/ResultTests.swift:
/// - see `func testSwiftCallRustReturnsResultString`
extension RustStringRef: Error {}
extension RustString: Vectorizable {
public static func vecOfSelfNew() -> UnsafeMutableRawPointer {
__swift_bridge__$Vec_RustString$new()
Expand Down Expand Up @@ -94,4 +97,4 @@ extension RustString: Vectorizable {
public static func vecOfSelfLen(vecPtr: UnsafeMutableRawPointer) -> UInt {
__swift_bridge__$Vec_RustString$len(vecPtr)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use super::{CodegenTest, ExpectedCHeader, ExpectedRustTokens, ExpectedSwiftCode}
use proc_macro2::TokenStream;
use quote::quote;

/// Test code generation for Rust function that accepts and returns a Result<T, E>
/// Test code generation for Rust function that accepts a Result<T, E>
/// where T and E are Strings.
mod extern_rust_fn_result_string {
use super::*;
Expand Down Expand Up @@ -64,6 +64,72 @@ void __swift_bridge__$some_function(struct __private__ResultPtrAndPtr arg);
}
}

/// Test code generation for Rust function that returns a Result<T, E>
/// where T and E are Strings.
mod extern_rust_fn_return_result_string {
use super::*;

fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
extern "Rust" {
fn some_function() -> Result<String, String>;
}
}
}
}

fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function(
) -> swift_bridge::result::ResultPtrAndPtr {
match super::some_function() {
Ok(ok) => {
swift_bridge::result::ResultPtrAndPtr {
is_ok: true,
ok_or_err: swift_bridge::string::RustString(ok).box_into_raw() as *mut std::ffi::c_void
}
}
Err(err) => {
swift_bridge::result::ResultPtrAndPtr {
is_ok: false,
ok_or_err: swift_bridge::string::RustString(err).box_into_raw() as *mut std::ffi::c_void
}
}
}
}
})
}

fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
public func some_function() throws -> RustString {
try { let val = __swift_bridge__$some_function(); if val.is_ok { return RustString(ptr: val.ok_or_err!) } else { throw RustString(ptr: val.ok_or_err!) } }()
}
"#,
)
}

const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ExactAfterTrim(
r#"
struct __private__ResultPtrAndPtr __swift_bridge__$some_function(void);
"#,
);

#[test]
fn extern_rust_fn_return_result_string() {
CodegenTest {
bridge_module: bridge_module_tokens().into(),
expected_rust_tokens: expected_rust_tokens(),
expected_swift_code: expected_swift_code(),
expected_c_header: EXPECTED_C_HEADER,
}
.test();
}
}

/// Test code generation for Rust function that accepts a Result<T, E> where T and E are
/// opaque Rust types.
mod extern_rust_fn_arg_result_opaque_rust {
Expand Down Expand Up @@ -449,7 +515,7 @@ public func some_function() throws -> SomeOkType {
r#"
typedef enum __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Tag {__swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$ResultOk, __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$ResultErr} __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Tag;
union __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Fields {void* ok; struct __swift_bridge__$SomeErrEnum err;};
typedef struct __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum{__swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Tag tag; union __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Fields payload;} __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum;
typedef struct __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum{__swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Tag tag; union __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum$Fields payload;} __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum;
"#,
r#"struct __swift_bridge__$ResultSomeOkTypeAndSomeErrEnum __swift_bridge__$some_function(void)"#,
])
Expand Down Expand Up @@ -531,7 +597,7 @@ public func some_function() throws -> SomeOkEnum {
r#"
typedef enum __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Tag {__swift_bridge__$ResultSomeOkEnumAndSomeErrType$ResultOk, __swift_bridge__$ResultSomeOkEnumAndSomeErrType$ResultErr} __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Tag;
union __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Fields {struct __swift_bridge__$SomeOkEnum ok; void* err;};
typedef struct __swift_bridge__$ResultSomeOkEnumAndSomeErrType{__swift_bridge__$ResultSomeOkEnumAndSomeErrType$Tag tag; union __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Fields payload;} __swift_bridge__$ResultSomeOkEnumAndSomeErrType;
typedef struct __swift_bridge__$ResultSomeOkEnumAndSomeErrType{__swift_bridge__$ResultSomeOkEnumAndSomeErrType$Tag tag; union __swift_bridge__$ResultSomeOkEnumAndSomeErrType$Fields payload;} __swift_bridge__$ResultSomeOkEnumAndSomeErrType;
"#,
r#"struct __swift_bridge__$ResultSomeOkEnumAndSomeErrType __swift_bridge__$some_function(void)"#,
])
Expand Down Expand Up @@ -606,7 +672,7 @@ public func some_function() throws -> () {
r#"
typedef enum __swift_bridge__$ResultVoidAndSomeErrEnum$Tag {__swift_bridge__$ResultVoidAndSomeErrEnum$ResultOk, __swift_bridge__$ResultVoidAndSomeErrEnum$ResultErr} __swift_bridge__$ResultVoidAndSomeErrEnum$Tag;
union __swift_bridge__$ResultVoidAndSomeErrEnum$Fields {struct __swift_bridge__$SomeErrEnum err;};
typedef struct __swift_bridge__$ResultVoidAndSomeErrEnum{__swift_bridge__$ResultVoidAndSomeErrEnum$Tag tag; union __swift_bridge__$ResultVoidAndSomeErrEnum$Fields payload;} __swift_bridge__$ResultVoidAndSomeErrEnum;
typedef struct __swift_bridge__$ResultVoidAndSomeErrEnum{__swift_bridge__$ResultVoidAndSomeErrEnum$Tag tag; union __swift_bridge__$ResultVoidAndSomeErrEnum$Fields payload;} __swift_bridge__$ResultVoidAndSomeErrEnum;
"#,
r#"struct __swift_bridge__$ResultVoidAndSomeErrEnum __swift_bridge__$some_function(void)"#,
])
Expand Down Expand Up @@ -689,7 +755,7 @@ public func some_function() throws -> (Int32, UInt32) {
r#"
typedef enum __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Tag {__swift_bridge__$ResultTupleI32U32AndSomeErrEnum$ResultOk, __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$ResultErr} __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Tag;
union __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Fields {struct __swift_bridge__$tuple$I32U32 ok; struct __swift_bridge__$SomeErrEnum err;};
typedef struct __swift_bridge__$ResultTupleI32U32AndSomeErrEnum{__swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Tag tag; union __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Fields payload;} __swift_bridge__$ResultTupleI32U32AndSomeErrEnum;
typedef struct __swift_bridge__$ResultTupleI32U32AndSomeErrEnum{__swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Tag tag; union __swift_bridge__$ResultTupleI32U32AndSomeErrEnum$Fields payload;} __swift_bridge__$ResultTupleI32U32AndSomeErrEnum;
"#,
r#"struct __swift_bridge__$ResultTupleI32U32AndSomeErrEnum __swift_bridge__$some_function(void)"#,
r#"typedef struct __swift_bridge__$tuple$I32U32 { int32_t _0; uint32_t _1; } __swift_bridge__$tuple$I32U32;"#,
Expand Down
9 changes: 9 additions & 0 deletions crates/swift-integration-tests/src/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ mod ffi {
) -> Result<ResultTestOpaqueRustType, ResultTestOpaqueRustType>;

fn rust_func_takes_result_string(arg: Result<String, String>);
fn rust_func_returns_result_string(ok: bool) -> Result<String, String>;

fn rust_func_takes_result_opaque_swift(
arg: Result<ResultTestOpaqueSwiftType, ResultTestOpaqueSwiftType>,
);
Expand Down Expand Up @@ -109,6 +111,13 @@ fn rust_func_takes_result_string(arg: Result<String, String>) {
}
}

fn rust_func_returns_result_string(ok: bool) -> Result<String, String> {
if !ok {
return Err("Error Message".to_string());
}
Ok("Success Message".to_string())
}

fn rust_func_reflect_result_opaque_rust(
arg: Result<ResultTestOpaqueRustType, ResultTestOpaqueRustType>,
) -> Result<ResultTestOpaqueRustType, ResultTestOpaqueRustType> {
Expand Down

0 comments on commit c45a38c

Please sign in to comment.