swift_bridge/std_bridge/
string.rs

1//! The corresponding C and Swift code can be found in
2//! crates/swift-bridge-build/src/generate_core/rust_string.{c.h,swift}
3pub use self::ffi::*;
4
5/// Ideally, we would bridge Rust's [`String`] to Swift's `String` type directly.
6/// We do not do this because there is no zero-copy way to create a Swift `String`, and, since
7/// `swift-bridge` aims to be useful in performance sensitive applications, we avoid unnecessary
8/// allocations.
9///
10/// Instead, users that wish to go from a `Rust std::string::String` to a Swift String must call
11/// `RustString.toString()` on the Swift side.
12/// We can consider introducing annotations that allow a user to opt in to an automatic conversion.
13/// For instance, something along the lines of:
14/// ```rust,no_run
15/// #[swift_bridge::bridge]
16/// mod ffi {
17///     extern "Rust" {
18///         #[swift_bridge(return_clone)]
19///         fn return_a_string() -> String;
20///
21///         #[swift_bridge(return_map_ok_clone)]
22///         fn return_a_string_ok() -> Result<String, ()>;
23///
24///         #[swift_bridge(return_map_err_clone)]
25///         fn return_a_string_err() -> Result<(), String>;
26///     }
27/// }
28/// ```
29/// When such an attribute was present `swift-bridge` would allocate a Swift String on the Swift
30/// side, instead of initializing an instance of the `RustString` class.
31///
32/// Such an automatic conversion could be made more efficient than using the `RustString.toString()`
33/// method to create a Swift String.
34/// For instance, to go from `Rust std::string::String -> Swift String` via a `RustString` we:
35/// - allocate a `class RustString` instance
36/// - call `RustString.toString()`, which constructs a Swift String using the `RustString`'s
37///    underlying buffer
38///
39/// An automatic conversion would look like:
40/// - construct a Swift String using the Rust `std::string::String`'s underlying buffer
41///
42/// Regardless of whether one is using `swift-bridge`, creating instances of Swift reference types
43/// requires a small heap allocation.
44/// By not creating an instance of the `RustString` class we would be eliminating one small
45/// allocation.
46///
47/// ## References
48/// - claim: Impossible to create a Swift `String` without copying:
49///   - `init(bytesNoCopy was deprecated in macOS 13` - https://forums.swift.org/t/init-bytesnocopy-was-deprecated-in-macos-13/61231
50///   - "String does not support no-copy initialization" - https://developer.apple.com/documentation/swift/string/init(bytesnocopy:length:encoding:freewhendone:)
51///   - `Does String(bytesNoCopy:) copy bytes?` - https://forums.swift.org/t/does-string-bytesnocopy-copy-bytes/51643
52/// - claim: Class instances allocate
53///   - "For example, a class instance (which allocates)" https://www.swift.org/documentation/server/guides/allocations.html#other-perf-tricks
54#[swift_bridge_macro::bridge(swift_bridge_path = crate)]
55mod ffi {
56    extern "Rust" {
57        type RustString;
58
59        #[swift_bridge(init)]
60        fn new() -> RustString;
61
62        #[swift_bridge(init)]
63        fn new_with_str(str: &str) -> RustString;
64
65        fn len(&self) -> usize;
66
67        fn as_str(&self) -> &str;
68
69        fn trim(&self) -> &str;
70    }
71}
72
73#[doc(hidden)]
74pub struct RustString(pub String);
75
76#[doc(hidden)]
77#[repr(C)]
78pub struct RustStr {
79    pub start: *const u8,
80    pub len: usize,
81}
82
83impl RustString {
84    fn new() -> Self {
85        RustString("".to_string())
86    }
87
88    fn new_with_str(str: &str) -> Self {
89        RustString(str.to_string())
90    }
91
92    fn len(&self) -> usize {
93        self.0.len()
94    }
95
96    fn as_str(&self) -> &str {
97        self.0.as_str()
98    }
99
100    fn trim(&self) -> &str {
101        self.0.trim()
102    }
103}
104
105impl RustString {
106    /// Box::into_raw(Box::new(self))
107    pub fn box_into_raw(self) -> *mut RustString {
108        Box::into_raw(Box::new(self))
109    }
110}
111
112impl RustStr {
113    pub fn len(&self) -> usize {
114        self.len
115    }
116
117    // TODO: Think through these lifetimes and the implications of them...
118    pub fn to_str<'a>(self) -> &'a str {
119        let bytes = unsafe { std::slice::from_raw_parts(self.start, self.len) };
120        std::str::from_utf8(bytes).expect("Failed to convert RustStr to &str")
121    }
122
123    pub fn to_string(self) -> String {
124        self.to_str().to_string()
125    }
126
127    pub fn from_str(str: &str) -> Self {
128        RustStr {
129            start: str.as_ptr(),
130            len: str.len(),
131        }
132    }
133}
134
135impl PartialEq for RustStr {
136    fn eq(&self, other: &Self) -> bool {
137        unsafe {
138            std::slice::from_raw_parts(self.start, self.len)
139                == std::slice::from_raw_parts(other.start, other.len)
140        }
141    }
142}
143
144#[export_name = "__swift_bridge__$RustStr$partial_eq"]
145#[allow(non_snake_case)]
146pub extern "C" fn __swift_bridge__RustStr_partial_eq(lhs: RustStr, rhs: RustStr) -> bool {
147    lhs == rhs
148}