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}