1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial |
3 | |
4 | //! module for the SharedString and related things |
5 | |
6 | #![allow (unsafe_code)] |
7 | #![warn (missing_docs)] |
8 | |
9 | use crate::SharedVector; |
10 | #[cfg (not(feature = "std" ))] |
11 | use alloc::string::String; |
12 | use core::fmt::{Debug, Display, Write}; |
13 | #[cfg (not(feature = "std" ))] |
14 | use core::iter::FromIterator; |
15 | use core::ops::Deref; |
16 | |
17 | /// This macro is the same as [`std::format!`], but it returns a [`SharedString`] instead. |
18 | /// |
19 | /// ### Example |
20 | /// ```rust |
21 | /// let s : slint::SharedString = slint::format!("Hello {}" , "world" ); |
22 | /// assert_eq!(s, slint::SharedString::from("Hello world" )); |
23 | /// ``` |
24 | #[macro_export ] |
25 | macro_rules! format { |
26 | ($($arg:tt)*) => {{ |
27 | $crate::string::format(core::format_args!($($arg)*)) |
28 | }} |
29 | } |
30 | |
31 | /// A string type used by the Slint run-time. |
32 | /// |
33 | /// SharedString uses implicit data sharing to make it efficient to pass around copies. When |
34 | /// cloning, a reference to the data is cloned, not the data itself. The data itself is only copied |
35 | /// when modifying it, for example using [push_str](SharedString::push_str). This is also called copy-on-write. |
36 | /// |
37 | /// Under the hood the string data is UTF-8 encoded and it is always terminated with a null character. |
38 | /// |
39 | /// `SharedString` implements [`Deref<Target=str>`] so it can be easily passed to any function taking a `&str`. |
40 | /// It also implement `From` such that it an easily be converted to and from the typical rust String type with `.into()` |
41 | #[derive (Clone, Default)] |
42 | #[repr (C)] |
43 | pub struct SharedString { |
44 | // Invariant: valid utf-8, `\0` terminated |
45 | inner: SharedVector<u8>, |
46 | } |
47 | |
48 | impl SharedString { |
49 | /// Creates a new empty string |
50 | /// |
51 | /// Same as `SharedString::default()` |
52 | pub fn new() -> Self { |
53 | Self::default() |
54 | } |
55 | |
56 | fn as_ptr(&self) -> *const u8 { |
57 | self.inner.as_ptr() |
58 | } |
59 | |
60 | /// Size of the string, in bytes. This excludes the terminating null character. |
61 | pub fn len(&self) -> usize { |
62 | self.inner.len().saturating_sub(1) |
63 | } |
64 | |
65 | /// Return true if the String is empty |
66 | pub fn is_empty(&self) -> bool { |
67 | self.len() == 0 |
68 | } |
69 | |
70 | /// Return a slice to the string |
71 | pub fn as_str(&self) -> &str { |
72 | // Safety: self.as_ptr is a pointer from the inner which has utf-8 |
73 | unsafe { |
74 | core::str::from_utf8_unchecked(core::slice::from_raw_parts(self.as_ptr(), self.len())) |
75 | } |
76 | } |
77 | |
78 | /// Append a string to this string |
79 | /// |
80 | /// ``` |
81 | /// # use i_slint_core::SharedString; |
82 | /// let mut hello = SharedString::from("Hello" ); |
83 | /// hello.push_str(", " ); |
84 | /// hello.push_str("World" ); |
85 | /// hello.push_str("!" ); |
86 | /// assert_eq!(hello, "Hello, World!" ); |
87 | /// ``` |
88 | pub fn push_str(&mut self, x: &str) { |
89 | let mut iter = x.as_bytes().iter().copied(); |
90 | if self.inner.is_empty() { |
91 | self.inner.extend(iter.chain(core::iter::once(0))); |
92 | } else if let Some(first) = iter.next() { |
93 | // We skip the `first` from `iter` because we will write it at the |
94 | // location of the previous `\0`, after extend did the re-alloc of the |
95 | // right size |
96 | let prev_len = self.len(); |
97 | self.inner.extend(iter.chain(core::iter::once(0))); |
98 | self.inner.make_mut_slice()[prev_len] = first; |
99 | } |
100 | } |
101 | } |
102 | |
103 | impl Deref for SharedString { |
104 | type Target = str; |
105 | fn deref(&self) -> &Self::Target { |
106 | self.as_str() |
107 | } |
108 | } |
109 | |
110 | impl From<&str> for SharedString { |
111 | fn from(value: &str) -> Self { |
112 | SharedString { |
113 | inner: SharedVector::from_iter( |
114 | value.as_bytes().iter().cloned().chain(core::iter::once(0)), |
115 | ), |
116 | } |
117 | } |
118 | } |
119 | |
120 | impl Debug for SharedString { |
121 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
122 | Debug::fmt(self.as_str(), f) |
123 | } |
124 | } |
125 | |
126 | impl Display for SharedString { |
127 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
128 | Display::fmt(self.as_str(), f) |
129 | } |
130 | } |
131 | |
132 | impl AsRef<str> for SharedString { |
133 | #[inline ] |
134 | fn as_ref(&self) -> &str { |
135 | self.as_str() |
136 | } |
137 | } |
138 | |
139 | #[cfg (feature = "serde" )] |
140 | impl serde::Serialize for SharedString { |
141 | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> |
142 | where |
143 | S: serde::Serializer, |
144 | { |
145 | let string: &str = self.as_str(); |
146 | serializer.serialize_str(string) |
147 | } |
148 | } |
149 | |
150 | #[cfg (feature = "serde" )] |
151 | impl<'de> serde::Deserialize<'de> for SharedString { |
152 | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> |
153 | where |
154 | D: serde::Deserializer<'de>, |
155 | { |
156 | let string: String = String::deserialize(deserializer)?; |
157 | Ok(SharedString::from(string)) |
158 | } |
159 | } |
160 | |
161 | #[cfg (feature = "std" )] |
162 | impl AsRef<std::ffi::CStr> for SharedString { |
163 | #[inline ] |
164 | fn as_ref(&self) -> &std::ffi::CStr { |
165 | if self.inner.is_empty() { |
166 | return Default::default(); |
167 | } |
168 | // Safety: we ensure that there is always a terminated \0 |
169 | debug_assert_eq!(self.inner.as_slice()[self.inner.len() - 1], 0); |
170 | unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(self.inner.as_slice()) } |
171 | } |
172 | } |
173 | |
174 | impl AsRef<[u8]> for SharedString { |
175 | #[inline ] |
176 | fn as_ref(&self) -> &[u8] { |
177 | self.as_str().as_bytes() |
178 | } |
179 | } |
180 | |
181 | impl<T> PartialEq<T> for SharedString |
182 | where |
183 | T: ?Sized + AsRef<str>, |
184 | { |
185 | fn eq(&self, other: &T) -> bool { |
186 | self.as_str() == other.as_ref() |
187 | } |
188 | } |
189 | impl Eq for SharedString {} |
190 | |
191 | impl<T> PartialOrd<T> for SharedString |
192 | where |
193 | T: ?Sized + AsRef<str>, |
194 | { |
195 | fn partial_cmp(&self, other: &T) -> Option<core::cmp::Ordering> { |
196 | PartialOrd::partial_cmp(self.as_str(), other.as_ref()) |
197 | } |
198 | } |
199 | impl Ord for SharedString { |
200 | fn cmp(&self, other: &Self) -> core::cmp::Ordering { |
201 | Ord::cmp(self.as_str(), other:other.as_str()) |
202 | } |
203 | } |
204 | |
205 | impl From<String> for SharedString { |
206 | fn from(s: String) -> Self { |
207 | s.as_str().into() |
208 | } |
209 | } |
210 | |
211 | impl From<&String> for SharedString { |
212 | fn from(s: &String) -> Self { |
213 | s.as_str().into() |
214 | } |
215 | } |
216 | |
217 | impl From<char> for SharedString { |
218 | fn from(c: char) -> Self { |
219 | SharedString::from(c.encode_utf8(&mut [0; 6]) as &str) |
220 | } |
221 | } |
222 | |
223 | impl From<SharedString> for String { |
224 | fn from(s: SharedString) -> String { |
225 | s.as_str().into() |
226 | } |
227 | } |
228 | |
229 | impl From<&SharedString> for String { |
230 | fn from(s: &SharedString) -> String { |
231 | s.as_str().into() |
232 | } |
233 | } |
234 | |
235 | impl core::ops::AddAssign<&str> for SharedString { |
236 | fn add_assign(&mut self, other: &str) { |
237 | self.push_str(other); |
238 | } |
239 | } |
240 | |
241 | impl core::ops::Add<&str> for SharedString { |
242 | type Output = SharedString; |
243 | fn add(mut self, other: &str) -> SharedString { |
244 | self.push_str(other); |
245 | self |
246 | } |
247 | } |
248 | |
249 | impl core::hash::Hash for SharedString { |
250 | fn hash<H: core::hash::Hasher>(&self, state: &mut H) { |
251 | self.as_str().hash(state) |
252 | } |
253 | } |
254 | |
255 | impl Write for SharedString { |
256 | fn write_str(&mut self, s: &str) -> core::fmt::Result { |
257 | self.push_str(s); |
258 | Ok(()) |
259 | } |
260 | } |
261 | |
262 | impl core::borrow::Borrow<str> for SharedString { |
263 | fn borrow(&self) -> &str { |
264 | self.as_str() |
265 | } |
266 | } |
267 | |
268 | /// Same as [`std::fmt::format()`], but return a [`SharedString`] instead |
269 | pub fn format(args: core::fmt::Arguments<'_>) -> SharedString { |
270 | // unfortunately, the estimated_capacity is unstable |
271 | //let capacity = args.estimated_capacity(); |
272 | let mut output: SharedString = SharedString::default(); |
273 | output.write_fmt(args).unwrap(); |
274 | output |
275 | } |
276 | |
277 | #[test ] |
278 | fn simple_test() { |
279 | let x: SharedString = SharedString::from("hello world!" ); |
280 | assert_eq!(x, "hello world!" ); |
281 | assert_ne!(x, "hello world?" ); |
282 | assert_eq!(x, x.clone()); |
283 | assert_eq!("hello world!" , x.as_str()); |
284 | let string: String = String::from("hello world!" ); |
285 | assert_eq!(x, string); |
286 | assert_eq!(x.to_string(), string); |
287 | let def: SharedString = SharedString::default(); |
288 | assert_eq!(def, SharedString::default()); |
289 | assert_eq!(def, SharedString::new()); |
290 | assert_ne!(def, x); |
291 | assert_eq!( |
292 | (&x as &dyn AsRef<std::ffi::CStr>).as_ref(), |
293 | &*std::ffi::CString::new("hello world!" ).unwrap() |
294 | ); |
295 | assert_eq!(SharedString::from('h' ), "h" ); |
296 | assert_eq!(SharedString::from('😎' ), "😎" ); |
297 | } |
298 | |
299 | #[test ] |
300 | fn threading() { |
301 | let shared_cst = SharedString::from("Hello there!" ); |
302 | let shared_mtx = std::sync::Arc::new(std::sync::Mutex::new(SharedString::from("Shared:" ))); |
303 | let mut handles = vec![]; |
304 | for _ in 0..20 { |
305 | let cst = shared_cst.clone(); |
306 | let mtx = shared_mtx.clone(); |
307 | handles.push(std::thread::spawn(move || { |
308 | assert_eq!(cst, "Hello there!" ); |
309 | let mut cst2 = cst.clone(); |
310 | cst2.push_str(" ... or not?" ); |
311 | assert_eq!(cst2, "Hello there! ... or not?" ); |
312 | assert_eq!(cst.clone(), "Hello there!" ); |
313 | |
314 | let shared = { |
315 | let mut lock = mtx.lock().unwrap(); |
316 | assert!(lock.starts_with("Shared:" )); |
317 | lock.push_str("!" ); |
318 | lock.clone() |
319 | }; |
320 | assert!(shared.clone().starts_with("Shared:" )); |
321 | })); |
322 | } |
323 | for j in handles { |
324 | j.join().unwrap(); |
325 | } |
326 | assert_eq!(shared_cst.clone(), "Hello there!" ); |
327 | assert_eq!(shared_mtx.lock().unwrap().as_str(), "Shared:!!!!!!!!!!!!!!!!!!!!" ); |
328 | // 20x"!" |
329 | } |
330 | |
331 | #[cfg (feature = "ffi" )] |
332 | pub(crate) mod ffi { |
333 | use super::*; |
334 | |
335 | /// for cbindgen. |
336 | #[allow (non_camel_case_types)] |
337 | type c_char = u8; |
338 | |
339 | #[no_mangle ] |
340 | /// Returns a nul-terminated pointer for this string. |
341 | /// The returned value is owned by the string, and should not be used after any |
342 | /// mutable function have been called on the string, and must not be freed. |
343 | pub extern "C" fn slint_shared_string_bytes(ss: &SharedString) -> *const c_char { |
344 | if ss.is_empty() { |
345 | " \0" .as_ptr() |
346 | } else { |
347 | ss.as_ptr() |
348 | } |
349 | } |
350 | |
351 | #[no_mangle ] |
352 | /// Destroy the shared string |
353 | pub unsafe extern "C" fn slint_shared_string_drop(ss: *const SharedString) { |
354 | core::ptr::read(ss); |
355 | } |
356 | |
357 | #[no_mangle ] |
358 | /// Increment the reference count of the string. |
359 | /// The resulting structure must be passed to slint_shared_string_drop |
360 | pub unsafe extern "C" fn slint_shared_string_clone(out: *mut SharedString, ss: &SharedString) { |
361 | core::ptr::write(out, ss.clone()) |
362 | } |
363 | |
364 | #[no_mangle ] |
365 | /// Safety: bytes must be a valid utf-8 string of size len without null inside. |
366 | /// The resulting structure must be passed to slint_shared_string_drop |
367 | pub unsafe extern "C" fn slint_shared_string_from_bytes( |
368 | out: *mut SharedString, |
369 | bytes: *const c_char, |
370 | len: usize, |
371 | ) { |
372 | let str = core::str::from_utf8(core::slice::from_raw_parts(bytes, len)).unwrap(); |
373 | core::ptr::write(out, SharedString::from(str)); |
374 | } |
375 | |
376 | /// Create a string from a number. |
377 | /// The resulting structure must be passed to slint_shared_string_drop |
378 | #[no_mangle ] |
379 | pub unsafe extern "C" fn slint_shared_string_from_number(out: *mut SharedString, n: f64) { |
380 | let str = crate::format!(" {}" , n as f32); |
381 | core::ptr::write(out, str); |
382 | } |
383 | |
384 | #[test ] |
385 | fn test_slint_shared_string_from_number() { |
386 | unsafe { |
387 | let mut s = core::mem::MaybeUninit::uninit(); |
388 | slint_shared_string_from_number(s.as_mut_ptr(), 45.); |
389 | assert_eq!(s.assume_init(), "45" ); |
390 | |
391 | let mut s = core::mem::MaybeUninit::uninit(); |
392 | slint_shared_string_from_number(s.as_mut_ptr(), 45.12); |
393 | assert_eq!(s.assume_init(), "45.12" ); |
394 | |
395 | let mut s = core::mem::MaybeUninit::uninit(); |
396 | slint_shared_string_from_number(s.as_mut_ptr(), -1325466.); |
397 | assert_eq!(s.assume_init(), "-1325466" ); |
398 | |
399 | let mut s = core::mem::MaybeUninit::uninit(); |
400 | slint_shared_string_from_number(s.as_mut_ptr(), 0.); |
401 | assert_eq!(s.assume_init(), "0" ); |
402 | |
403 | let mut s = core::mem::MaybeUninit::uninit(); |
404 | slint_shared_string_from_number( |
405 | s.as_mut_ptr(), |
406 | ((1235.82756f32 * 1000f32).round() / 1000f32) as _, |
407 | ); |
408 | assert_eq!(s.assume_init(), "1235.828" ); |
409 | } |
410 | } |
411 | |
412 | /// Append some bytes to an existing shared string |
413 | /// |
414 | /// bytes must be a valid utf8 array of size `len`, without null bytes inside |
415 | #[no_mangle ] |
416 | pub unsafe extern "C" fn slint_shared_string_append( |
417 | self_: &mut SharedString, |
418 | bytes: *const c_char, |
419 | len: usize, |
420 | ) { |
421 | let str = core::str::from_utf8(core::slice::from_raw_parts(bytes, len)).unwrap(); |
422 | self_.push_str(str); |
423 | } |
424 | #[test ] |
425 | fn test_slint_shared_string_append() { |
426 | let mut s = SharedString::default(); |
427 | let mut append = |x: &str| unsafe { |
428 | slint_shared_string_append(&mut s, x.as_bytes().as_ptr(), x.len()); |
429 | }; |
430 | append("Hello" ); |
431 | append(", " ); |
432 | append("world" ); |
433 | append("" ); |
434 | append("!" ); |
435 | assert_eq!(s.as_str(), "Hello, world!" ); |
436 | } |
437 | } |
438 | |
439 | #[cfg (feature = "serde" )] |
440 | #[test ] |
441 | fn test_serialize_deserialize_sharedstring() { |
442 | let v: SharedString = SharedString::from("data" ); |
443 | let serialized: String = serde_json::to_string(&v).unwrap(); |
444 | let deserialized: SharedString = serde_json::from_str(&serialized).unwrap(); |
445 | assert_eq!(v, deserialized); |
446 | } |
447 | |