Skip to content

Add Sendable to all async return types in tests#312

Merged
chinedufn merged 4 commits intochinedufn:masterfrom
colinmarc:async-test-sendable
Aug 27, 2025
Merged

Add Sendable to all async return types in tests#312
chinedufn merged 4 commits intochinedufn:masterfrom
colinmarc:async-test-sendable

Conversation

@colinmarc
Copy link
Copy Markdown
Contributor

@colinmarc colinmarc commented Jan 27, 2025

Without this fix I get errors like the following under Swift 6:

/home/colinmarc/dev/swift-bridge/integration-tests/Sources/SharedLib/Generated/SharedLib.swift:4159:26: error: sending 'rustFnRetVal' risks causing data races
4157 |     return try await withCheckedThrowingContinuation({ (continuation: CheckedContinuation<AsyncResultOkEnum, Error>) in
4158 |         let callback = { rustFnRetVal in
4159 |             continuation.resume(with: rustFnRetVal)
     |                          |- error: sending 'rustFnRetVal' risks causing data races
     |                          `- note: task-isolated 'rustFnRetVal' is passed as a 'sending' parameter; Uses in callee may race with later task-isolated uses
4160 |         }
4161 |

I avoided adding Sendable for RustString since that's being discussed in #296 and elsewhere. Instead, I commented it out and left a TODO.

Fixes #311.

Copy link
Copy Markdown
Owner

@chinedufn chinedufn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor feedback around documentation and changing a String to a u32, then we can land this

extension SameEnum: @unchecked Sendable {}
extension SameEnum: Error {}

extension AsyncResultOkEnum: @unchecked Sendable {}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm we'll want to move away from these @unchecked Sendable {}s in favor of enabling swift-bridge to emit Sendable extension.

In the meantime, you can make this @unchecked Sendable safe by replacing the inner String with a primitive such as u32.

enum AsyncResultOkEnum {
NoFields,
UnnamedFields(i32, String),
NamedFields { value: u8 },
}

Let's also leave a TODO in this file to ditch @unchecked Sendable in favor of proper Sendable support. Can link to #269

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For future reference, I think you linked the wrong issue by accident - the relevant one is #150.

}
}

extension AsyncRustFnReturnStruct: @unchecked Sendable {}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently safe since this struct only contains a primitive

#[swift_bridge(swift_repr = "struct")]
struct AsyncRustFnReturnStruct {
field: u8,
}

But we'll still eventually want to replace all of these @unchecked Sendable with proper swift-bridge support for emitting Sendable impls as described in #269

Comment on lines +16 to +17
// TODO: this is broken because RustString is not Sendable.
// async fn rust_async_reflect_string(string: String) -> String;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TODO describes the problem but not what we should do about it.

Do we need to do research? Do we already know the solution? Is the problem unsolvable? etc.
Let's make it more clear here what the next step is on this TODO.

@colinmarc colinmarc force-pushed the async-test-sendable branch from 48f774b to 859fd09 Compare February 2, 2025 09:55
@colinmarc
Copy link
Copy Markdown
Contributor Author

I think I addressed your comments.

Copy link
Copy Markdown
Owner

@chinedufn chinedufn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good just one request.

Can you look over #309 (comment) and confirm that that reasoning is sound?

If so, please add the String code back that you commented out in this PR.

Comment on lines +25 to +28
// TODO: this is broken because RustString is not Sendable.
// Work around making String and other opaque types Sendable is tracked
// here: https://github.com/chinedufn/swift-bridge/issues/150
// async fn rust_async_reflect_string(string: String) -> String;
Copy link
Copy Markdown
Owner

@chinedufn chinedufn Feb 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RustString should be safe to implement Sendable for today, so long as the Swift code does not make multiple copies of the RustString class.

It will be fully safe once we start using ~Copyable to guarantee that the Swift code cannot copy the RustString (copy as in, copy the class RustString, which is a pointer to the underlying Rust std::string::String.).

So, this code can be added back.


Safety claims are here #309 (comment)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uncommenting this line makes it fail to compile on Swift 6, and I think changing core code in this PR would be out of scope, no?

@colinmarc
Copy link
Copy Markdown
Contributor Author

Hm, I don't think RustStr should be made sendable until the work in #155 lands. I would merge this as-is and then follow up reenabling the String-based tests after it does.

@chinedufn
Copy link
Copy Markdown
Owner

chinedufn commented Feb 27, 2025

We should make it Sendable and, for now, put the onus on the user to maintain the safety rules outlined in the book.
We shouldn't prevent people from returning String's simply because it's possible to misuse a String.
That's unnecessarily limiting.

If users aren't following the book's safety rules then they're going to hit UB regardless of whether we implement Sendable.

If in the future we're able to guarantee safety at compile time, even better. For now, Swift is fairly limited in this regard.


Swift does not yet make it possible for a fully safe FFI boundary. We need to accept that and work with it, rather than wishing that the Swift side had Rust's level of memory safety.

In time we hope that it will get easier and easier to make the Swift side safer and safer.
For now users are responsible for some kinds of memory safety.

@colinmarc
Copy link
Copy Markdown
Contributor Author

Regardless of how you want to proceed on the RustStr: Sendable stuff, that needs a fix before I can uncomment the line here. I think I can proceed on #307 without this, however, as long as I don't port the async tests over to the new harness yet.

@debanjanbasu
Copy link
Copy Markdown

Any idea, when this would be merged 🤔?

@chinedufn chinedufn merged commit 5350b06 into chinedufn:master Aug 27, 2025
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Async test return values aren't Sendable, so tests fail to compile

3 participants