-
Notifications
You must be signed in to change notification settings - Fork 65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove Error
and Sendable
implementations for RustStringRef
#309
Comments
This issue should be closed since, as long as Rust's aliasing rules are followed, a
So, after Swift ownership (i.e. Here's an example of Rust code that passes a For example, the following Rust code compiles, which demonstrates that a Rust playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=a0f25b32652be6df51993f01b8d51728 let mut string = "hello".to_string();
let string_ref_mut = &mut string;
std::thread::scope(move |scope| {
let string_ref_mut = string_ref_mut;
scope.spawn(move || {
string_ref_mut.push_str(" world");
});
});
assert_eq!(string, "hello world"); |
Agreed, as long as...
So I think
|
Supporting Swift's ownership will allow us to reduce that list of rules, but unless Swift releases more support for lifetimes and borrowing we won't get that list down to zero. Here is an example of a rule that can be deleted once Swift ownership is implemented, since it will automatically enforced: https://github.com/chinedufn/swift-bridge/blob/master/book/src/safety/README.md?plain=1#L122-L148
swift-bridge/book/src/safety/README.md Lines 10 to 18 in c15279c
Our current approach is to trust that the user will follow these rules, thereby enabling us to release functionality that relies on these rules. If you are not following those memory safety rules then your program will be riddled with undefined behavior regardless of whether or not In short, we're putting max power/performance into your fingertips first, while simultaneously introducing new ways to reduce the number of mistakes that you'll make. Therefore, we should keep In short, if you aren't following those memory safety rules your program is already exhibiting UB, and if you are following them then Let me know if you take issue with this approach. If not, I'll close out #310 |
I understand your argument, and of course it's your call in the end. I think I'd still draw a distinction between "we give you these unsafe tools, here are the rules for using them" and "we're marking a type as Sendable even though it's extremely not". The latter, to me, amounts to incorrect documentation. The user has tools to circumvent swift's restrictions if they really want to; you can wholesale disable swift's concurrency checks at compile-time if you want, or they could mark their error type as Finally, I'll point out that without #310, swift-bridge's generated code simply does not compile on recent swift versions. Like I said, totally your call! Please feel free to close #310 if that's your decision, I don't mind. I would like to work more on #307, and I can't get it to compile on master right now - that was the motivation. |
The main wrench is that to implement I started working on adding ownership support to Along the way I ran into a problem, if your type is Meaning, as it stands now, if we implemented the Swift ownership proposal from #155 (comment) then I opened an issue in If the maintainers don't reply in the next few days then we can revert Or, better yet, how about this:
And if someone asks why the behavior regressed we can point them towards that comment (or link to this issue if you want put the checklist of things to uncomment into this issue). If that works for you we can revert the Here's how the new // Rust type
#[repr(C)]
pub struct RustString {
ptr: *mut u8,
len: usize,
capacity: usize,
}
impl RustString {
fn into_std_string(self) -> String {
unsafe { String::from_raw_parts(self.ptr, self.len, self.capacity) }
}
// Used by the bridge macro to convert a `std::string::String` into a `RustString`.
#[doc(hidden)]
pub fn from_std_string(mut string: String) -> Self {
let rust_string = Self {
ptr: string.as_mut_ptr(),
len: string.len(),
capacity: string.capacity(),
};
std::mem::forget(string);
rust_string
}
} // C header
typedef struct __swift_bridge__RustString { uint8_t* const ptr; uintptr_t len; uintptr_t capacity } __swift_bridge__RustString; // Swift
/// A string that was created in Rust.
public struct RustString: ~Copyable {
private var string: __swift_bridge__RustString
public init() {
self.string = __swift_bridge__$RustString$new()
}
public init<GenericToRustStr: ToRustStr>(_ str: GenericToRustStr) {
str.toRustStr({ strAsRustStr in
self.string = __swift_bridge__$RustString$new_with_str(strAsRustStr)
})
}
/// Get a pointer to the string's UTF-8 buffer.
func asPtr() -> UnsafePointer<UInt8> {
let ptr = self.string.ptr!
return UnsafePointer(ptr)
}
/// Get the string's length.
func len() -> UInt {
self.string.len
}
/// Return a reference to the string's UTF-8 byte buffer.
func asStr() -> RustStr {
RustStr(start: self.string.ptr, len: self.string.len)
}
func trim() -> RustStr {
self.asStr().trim()
}
/// Convert the `RustString` into a Swift `String`.
func toString() -> String {
let rust_str = self.asStr();
let swift_string = rust_str.toString()
return swift_string
}
deinit {
__swift_bridge__$RustString$_free(self.string)
}
} A nice advantage is that before this new implementation we passed Rust's In the new implementation we pass the Rust std::string::String from here's the implementation that is currently in the master branch (that the above sketch should replace once swiftlang/swift#79141 is addressed) swift-bridge/src/std_bridge/string.rs Lines 5 to 22 in 4827500
|
It seems like I raised this at some point earlier, but what about just copying the error into an immutable string on the swift side? I think in the general case, it makes sense to have That would mean that:
|
Ideally transparent structs/enums would be non-copyable so that ownership could be enforced. What would this attribute do exactly?
The only reason When I checked in Dec 2021 there was no way to create a Swift To avoid scenarios where a user experiences surprisingly bad performance when dealing with large strings, we want to avoid implicitly copying a potentiall a Rust perhaps something like: #[swift_bridge::bridge]
mod ffi {
extern "Rust" {
#[swift_bridge(some_attribute_that_indciates_the_string_will_be_cloned)]
fn return_result() -> Result<(), String>;
}
} Then on the Swift side the user would get a Swift An alternative design would be using a different type in the bridge module that is explicitly documented to always lead to copying the string's bytes. #[swift_bridge::bridge]
mod ffi {
extern "Rust" {
fn return_result() -> Result<(), SwiftString>;
}
}
fn return_result() -> Result<(), std::string::String> {
Err("".to_string())
} When we see this
from the Swift
This gives me the impression that |
^ both Ideal designs would be:
Neither of those ideal designs are possible today (unless something has changed in the last few years that makes it possible to create a Swift String with zero-copying. I haven't looked back into that.) |
It turns out that noncopyable Swift types are more limited than I realized. I wrote a bit about this here #155 (comment) In short, they can't implement Swift protocols like So, switching So, we'll keep Here are my propose next steps:
Reasons to go with it:
I'll proceed with implementing this. I'll wait at least a couple days to think it over. If you have any new points/challenges feel free to share. |
Sorry for pushing back so much - I just disagree and I'm argumentative by nature :) Feel free to ignore/override any of the below, I won't take it personally! Immutable strings not necessarily slowerI agree with your contention that copying around heap strings is not an inherently fast operation. However, immutable, copy-on-write strings are not an anti-pattern! Golang and Swift are both fast languages that insist on immutable strings, and are comfortable with the performance properties of them. People using
|
Originally reported: #296 (comment)
The
Error
protocol was implemented forRustStringRef
in #296The
RustStringRef
class looks like:Up until Swift 5.6,
UnsafeMutableRawPointer
implemented theSendable
protocol, meaning it was possible to "safely" implement theError
protocol on a class that contains anUnsafeMutableRawPointer
(Error
protocol requiresSendable
).As of Swift 5.6
UnsafeMutableRawPointer
no longer implementsSendable
. https://github.com/swiftlang/swift-evolution/blob/main/proposals/0331-remove-sendable-from-unsafepointer.mdWe should remove the
Error
implementation fromRustStringRef
.This is a thread safety issue, so we can release this breaking change in a
0.1.x
release.We won't promise stability until
swift-bridge
hits version1.0.0
.Here's where
Error
is implemented:swift-bridge/crates/swift-bridge-build/src/generate_core/rust_string.swift
Lines 50 to 52 in c45a38c
The text was updated successfully, but these errors were encountered: