Skip to content

Commit 27d7be9

Browse files
authored
Support returning Result<(), OpaqueRust> from Rust functions (#180)
This commit adds support for returning `Result<(), OpaqueRust>` but not passing as an arg. The change to `examples/rust-binary-calls-swift-package/build.rs` was to support xcode not existing in the default location (I use the `xcodes` tool). This commit also supports empty types, such as `struct UnitStruct()`. This enables the following: ```rust #[swift_bridge::bridge] mod ffi { struct UnitType; extern "Rust" { type OpaqueType; fn null_result() -> Result<(), OpaqueType>; fn unit_result() -> Result<UnitType, OpaqueType>; } } fn null_result() -> Result<(), OpaqueType> { Ok(()) } fn unit_result() -> Result<UnitType, OpaqueType> { Ok(UnitType {}) } ```
1 parent b13c56a commit 27d7be9

File tree

13 files changed

+323
-39
lines changed

13 files changed

+323
-39
lines changed

SwiftRustIntegrationTestRunner/SwiftRustIntegrationTestRunnerTests/ResultTests.swift

+24
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,28 @@ class ResultTests: XCTestCase {
4040
.Err(ResultTestOpaqueSwiftType(val: 666))
4141
)
4242
}
43+
44+
/// Verify that we can receive a Result<(), OpaqueRust> from Rust
45+
func testSwiftCallRustResultNullOpaqueRust() throws {
46+
try! rust_func_return_result_null_opaque_rust(true)
47+
48+
do {
49+
try rust_func_return_result_null_opaque_rust(false)
50+
XCTFail("The function should have returned an error.")
51+
} catch let error as ResultTestOpaqueRustType {
52+
XCTAssertEqual(error.val(), 222)
53+
}
54+
}
55+
56+
/// Verify that we can receive a Result<UnitStruct, OpaqueRust> from Rust
57+
func testSwiftCallRustResultUnitStructOpaqueRust() throws {
58+
try! rust_func_return_result_unit_struct_opaque_rust(true)
59+
60+
do {
61+
try rust_func_return_result_unit_struct_opaque_rust(false)
62+
XCTFail("The function should have returned an error.")
63+
} catch let error as ResultTestOpaqueRustType {
64+
XCTAssertEqual(error.val(), 222)
65+
}
66+
}
4367
}

crates/swift-bridge-build/src/generate_core.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use crate::generate_core::boxed_fn_support::{
22
C_CALLBACK_SUPPORT_NO_ARGS_NO_RETURN, SWIFT_CALLBACK_SUPPORT_NO_ARGS_NO_RETURN,
33
};
4-
use crate::generate_core::result_support::{C_RESULT_SUPPORT, SWIFT_RUST_RESULT};
4+
use crate::generate_core::result_support::{
5+
C_RESULT_SUPPORT, C_RESULT_VOID_SUPPORT, SWIFT_RUST_RESULT,
6+
};
57
use std::path::Path;
68

79
const RUST_STRING_SWIFT: &'static str = include_str!("./generate_core/rust_string.swift");
@@ -33,6 +35,8 @@ pub(super) fn write_core_swift_and_c(out_dir: &Path) {
3335
c_header += &C_CALLBACK_SUPPORT_NO_ARGS_NO_RETURN;
3436
c_header += "\n";
3537
c_header += &C_RESULT_SUPPORT;
38+
c_header += "\n";
39+
c_header += &C_RESULT_VOID_SUPPORT;
3640

3741
std::fs::write(core_c_header_out, c_header).unwrap();
3842
}

crates/swift-bridge-build/src/generate_core/result_support.rs

+4
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,7 @@ extension RustResult {
3838
pub const C_RESULT_SUPPORT: &'static str = r#"
3939
struct __private__ResultPtrAndPtr { bool is_ok; void* ok_or_err; };
4040
"#;
41+
42+
pub const C_RESULT_VOID_SUPPORT: &'static str = r#"
43+
struct __private__ResultVoidAndPtr { bool is_ok; void* err; };
44+
"#;

crates/swift-bridge-ir/src/bridged_type.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,11 @@ pub(crate) trait BridgeableType: Debug {
5858
!self.is_built_in_type()
5959
}
6060

61-
/// Whether or not this type can be encoded to exactly one representation.
61+
/// Whether or not this type can be encoded to exactly one representation,
62+
/// and therefore can be encoded with zero bytes.
6263
/// For example `()` and `struct Foo;` can have exactly one representation,
6364
/// but `u8` cannot since there are 255 possible `u8`s.
64-
fn has_exactly_one_encoding(&self) -> bool {
65+
fn can_be_encoded_with_zero_bytes(&self) -> bool {
6566
self.only_encoding().is_some()
6667
}
6768

crates/swift-bridge-ir/src/bridged_type/bridgeable_result.rs

+87-25
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,14 @@ impl BuiltInResult {
2424
// TODO: Choose the kind of Result representation based on whether or not the ok and error
2525
// types are primitives.
2626
// See `swift-bridge/src/std_bridge/result`
27-
let result_kind = quote! {
28-
ResultPtrAndPtr
27+
let result_kind = if self.ok_ty.can_be_encoded_with_zero_bytes() {
28+
quote! {
29+
ResultVoidAndPtr
30+
}
31+
} else {
32+
quote! {
33+
ResultPtrAndPtr
34+
}
2935
};
3036

3137
quote! {
@@ -54,18 +60,37 @@ impl BuiltInResult {
5460
span,
5561
);
5662

57-
quote! {
58-
match #expression {
59-
Ok(ok) => {
60-
#swift_bridge_path::result::ResultPtrAndPtr {
61-
is_ok: true,
62-
ok_or_err: #convert_ok as *mut std::ffi::c_void
63+
if self.ok_ty.can_be_encoded_with_zero_bytes() {
64+
quote! {
65+
match #expression {
66+
Ok(ok) => {
67+
#swift_bridge_path::result::ResultVoidAndPtr {
68+
is_ok: true,
69+
err: std::ptr::null_mut::<std::ffi::c_void>()
70+
}
71+
}
72+
Err(err) => {
73+
#swift_bridge_path::result::ResultVoidAndPtr {
74+
is_ok: false,
75+
err: #convert_err as *mut std::ffi::c_void
76+
}
6377
}
6478
}
65-
Err(err) => {
66-
#swift_bridge_path::result::ResultPtrAndPtr {
67-
is_ok: false,
68-
ok_or_err: #convert_err as *mut std::ffi::c_void
79+
}
80+
} else {
81+
quote! {
82+
match #expression {
83+
Ok(ok) => {
84+
#swift_bridge_path::result::ResultPtrAndPtr {
85+
is_ok: true,
86+
ok_or_err: #convert_ok as *mut std::ffi::c_void
87+
}
88+
}
89+
Err(err) => {
90+
#swift_bridge_path::result::ResultPtrAndPtr {
91+
is_ok: false,
92+
ok_or_err: #convert_err as *mut std::ffi::c_void
93+
}
6994
}
7095
}
7196
}
@@ -129,18 +154,44 @@ impl BuiltInResult {
129154
type_pos: TypePosition,
130155
types: &TypeDeclarations,
131156
) -> String {
132-
let convert_ok =
133-
self.ok_ty
134-
.convert_ffi_expression_to_swift_type("val.ok_or_err!", type_pos, types);
135-
let convert_err =
136-
self.err_ty
137-
.convert_ffi_expression_to_swift_type("val.ok_or_err!", type_pos, types);
157+
let (mut ok, err) = if let Some(zero_byte_encoding) = self.ok_ty.only_encoding() {
158+
let ok = zero_byte_encoding.swift;
159+
let convert_err = self
160+
.err_ty
161+
.convert_ffi_expression_to_swift_type("val.err!", type_pos, types);
162+
163+
(ok, convert_err)
164+
} else {
165+
let convert_ok =
166+
self.ok_ty
167+
.convert_ffi_expression_to_swift_type("val.ok_or_err!", type_pos, types);
168+
let convert_err =
169+
self.err_ty
170+
.convert_ffi_expression_to_swift_type("val.ok_or_err!", type_pos, types);
171+
172+
(convert_ok, convert_err)
173+
};
174+
175+
// There is a Swift compiler bug in Xcode 13 where using an explicit `()` here somehow leads
176+
// the Swift compiler to a compile time error:
177+
// "Unable to infer complex closure return type; add explicit type to disambiguate"
178+
//
179+
// It's asking us to add a `{ () -> () in .. }` explicit type to the beginning of our closure.
180+
//
181+
// To solve this bug we can either add that explicit closure type, or remove the explicit
182+
// `return ()` in favor of a `return`.. Not sure why making the return type less explicit
183+
// solves the compile time error.. But it does..
184+
//
185+
// As mentioned, this doesn't seem to happen in Xcode 14.
186+
// So, we can remove this if statement whenever we stop supporting Xcode 13.
187+
if self.ok_ty.is_null() {
188+
ok = "".to_string();
189+
}
138190

139191
format!(
140192
"try {{ let val = {expression}; if val.is_ok {{ return {ok} }} else {{ throw {err} }} }}()",
141193
expression = expression,
142-
ok = convert_ok,
143-
err = convert_err
194+
err = err
144195
)
145196
}
146197

@@ -156,17 +207,28 @@ impl BuiltInResult {
156207
.err_ty
157208
.convert_swift_expression_to_ffi_type("err", type_pos);
158209

159-
format!(
160-
"{{ switch {val} {{ case .Ok(let ok): return __private__ResultPtrAndPtr(is_ok: true, ok_or_err: {convert_ok}) case .Err(let err): return __private__ResultPtrAndPtr(is_ok: false, ok_or_err: {convert_err}) }} }}()",
161-
val = expression
162-
)
210+
if self.ok_ty.can_be_encoded_with_zero_bytes() {
211+
format!(
212+
"{{ switch {val} {{ case .Ok(let ok): return __private__ResultVoidAndPtr(is_ok: true, err: nil) case .Err(let err): return __private__ResultVoidAndPtr(is_ok: false, err: {convert_err}) }} }}()",
213+
val = expression
214+
)
215+
} else {
216+
format!(
217+
"{{ switch {val} {{ case .Ok(let ok): return __private__ResultPtrAndPtr(is_ok: true, ok_or_err: {convert_ok}) case .Err(let err): return __private__ResultPtrAndPtr(is_ok: false, ok_or_err: {convert_err}) }} }}()",
218+
val = expression
219+
)
220+
}
163221
}
164222

165223
pub fn to_c(&self) -> &'static str {
166224
// TODO: Choose the kind of Result representation based on whether or not the ok and error
167225
// types are primitives.
168226
// See `swift-bridge/src/std_bridge/result`
169-
"struct __private__ResultPtrAndPtr"
227+
if self.ok_ty.can_be_encoded_with_zero_bytes() {
228+
"struct __private__ResultVoidAndPtr"
229+
} else {
230+
"struct __private__ResultPtrAndPtr"
231+
}
170232
}
171233
}
172234

crates/swift-bridge-ir/src/codegen/codegen_tests/result_codegen_tests.rs

+141
Original file line numberDiff line numberDiff line change
@@ -263,3 +263,144 @@ void __swift_bridge__$some_function(struct __private__ResultPtrAndPtr arg);
263263
.test();
264264
}
265265
}
266+
267+
/// Test code generation for Rust function that accepts a Result<(), E> where E is an
268+
/// opaque Rust type.
269+
mod extern_rust_fn_return_result_null_and_opaque_rust {
270+
use super::*;
271+
272+
fn bridge_module_tokens() -> TokenStream {
273+
quote! {
274+
mod ffi {
275+
extern "Rust" {
276+
type SomeType;
277+
278+
fn some_function () -> Result<(), SomeType>;
279+
}
280+
}
281+
}
282+
}
283+
284+
fn expected_rust_tokens() -> ExpectedRustTokens {
285+
ExpectedRustTokens::Contains(quote! {
286+
#[export_name = "__swift_bridge__$some_function"]
287+
pub extern "C" fn __swift_bridge__some_function() -> swift_bridge::result::ResultVoidAndPtr {
288+
match super::some_function() {
289+
Ok(ok) => {
290+
swift_bridge::result::ResultVoidAndPtr {
291+
is_ok: true,
292+
err: std::ptr::null_mut::<std::ffi::c_void>()
293+
}
294+
}
295+
Err(err) => {
296+
swift_bridge::result::ResultVoidAndPtr {
297+
is_ok: false,
298+
err: Box::into_raw(Box::new({
299+
let val: super::SomeType = err;
300+
val
301+
})) as *mut super::SomeType as *mut std::ffi::c_void
302+
}
303+
}
304+
}
305+
}
306+
})
307+
}
308+
309+
fn expected_swift_code() -> ExpectedSwiftCode {
310+
ExpectedSwiftCode::ContainsAfterTrim(
311+
r#"
312+
public func some_function() throws -> () {
313+
try { let val = __swift_bridge__$some_function(); if val.is_ok { return } else { throw SomeType(ptr: val.err!) } }()
314+
}
315+
"#,
316+
)
317+
}
318+
319+
const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ContainsAfterTrim(
320+
r#"
321+
struct __private__ResultVoidAndPtr __swift_bridge__$some_function(void);
322+
"#,
323+
);
324+
325+
#[test]
326+
fn extern_rust_fn_return_result_opaque_rust() {
327+
CodegenTest {
328+
bridge_module: bridge_module_tokens().into(),
329+
expected_rust_tokens: expected_rust_tokens(),
330+
expected_swift_code: expected_swift_code(),
331+
expected_c_header: EXPECTED_C_HEADER,
332+
}
333+
.test();
334+
}
335+
}
336+
337+
// Test code generation for Rust function that accepts a Result<T, E> where T is a UnitStruct and E is an
338+
/// opaque Rust type.
339+
mod extern_rust_fn_return_result_unit_and_opaque_rust {
340+
use super::*;
341+
342+
fn bridge_module_tokens() -> TokenStream {
343+
quote! {
344+
mod ffi {
345+
struct UnitType;
346+
extern "Rust" {
347+
type SomeType;
348+
349+
fn some_function () -> Result<UnitType, SomeType>;
350+
}
351+
}
352+
}
353+
}
354+
355+
fn expected_rust_tokens() -> ExpectedRustTokens {
356+
ExpectedRustTokens::Contains(quote! {
357+
#[export_name = "__swift_bridge__$some_function"]
358+
pub extern "C" fn __swift_bridge__some_function() -> swift_bridge::result::ResultVoidAndPtr {
359+
match super::some_function() {
360+
Ok(ok) => {
361+
swift_bridge::result::ResultVoidAndPtr {
362+
is_ok: true,
363+
err: std::ptr::null_mut::<std::ffi::c_void>()
364+
}
365+
}
366+
Err(err) => {
367+
swift_bridge::result::ResultVoidAndPtr {
368+
is_ok: false,
369+
err: Box::into_raw(Box::new({
370+
let val: super::SomeType = err;
371+
val
372+
})) as *mut super::SomeType as *mut std::ffi::c_void
373+
}
374+
}
375+
}
376+
}
377+
})
378+
}
379+
380+
fn expected_swift_code() -> ExpectedSwiftCode {
381+
ExpectedSwiftCode::ContainsAfterTrim(
382+
r#"
383+
public func some_function() throws -> UnitType {
384+
try { let val = __swift_bridge__$some_function(); if val.is_ok { return UnitType() } else { throw SomeType(ptr: val.err!) } }()
385+
}
386+
"#,
387+
)
388+
}
389+
390+
const EXPECTED_C_HEADER: ExpectedCHeader = ExpectedCHeader::ContainsAfterTrim(
391+
r#"
392+
struct __private__ResultVoidAndPtr __swift_bridge__$some_function(void);
393+
"#,
394+
);
395+
396+
#[test]
397+
fn extern_rust_fn_return_result_unit_and_opaque_rust() {
398+
CodegenTest {
399+
bridge_module: bridge_module_tokens().into(),
400+
expected_rust_tokens: expected_rust_tokens(),
401+
expected_swift_code: expected_swift_code(),
402+
expected_c_header: EXPECTED_C_HEADER,
403+
}
404+
.test();
405+
}
406+
}

0 commit comments

Comments
 (0)