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
9use crate::SharedVector;
10#[cfg(not(feature = "std"))]
11use alloc::string::String;
12use core::fmt::{Debug, Display, Write};
13#[cfg(not(feature = "std"))]
14use core::iter::FromIterator;
15use 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]
25macro_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)]
43pub struct SharedString {
44 // Invariant: valid utf-8, `\0` terminated
45 inner: SharedVector<u8>,
46}
47
48impl 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
103impl Deref for SharedString {
104 type Target = str;
105 fn deref(&self) -> &Self::Target {
106 self.as_str()
107 }
108}
109
110impl 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
120impl 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
126impl 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
132impl AsRef<str> for SharedString {
133 #[inline]
134 fn as_ref(&self) -> &str {
135 self.as_str()
136 }
137}
138
139#[cfg(feature = "serde")]
140impl 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")]
151impl<'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")]
162impl 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
174impl AsRef<[u8]> for SharedString {
175 #[inline]
176 fn as_ref(&self) -> &[u8] {
177 self.as_str().as_bytes()
178 }
179}
180
181impl<T> PartialEq<T> for SharedString
182where
183 T: ?Sized + AsRef<str>,
184{
185 fn eq(&self, other: &T) -> bool {
186 self.as_str() == other.as_ref()
187 }
188}
189impl Eq for SharedString {}
190
191impl<T> PartialOrd<T> for SharedString
192where
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}
199impl 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
205impl From<String> for SharedString {
206 fn from(s: String) -> Self {
207 s.as_str().into()
208 }
209}
210
211impl From<&String> for SharedString {
212 fn from(s: &String) -> Self {
213 s.as_str().into()
214 }
215}
216
217impl From<char> for SharedString {
218 fn from(c: char) -> Self {
219 SharedString::from(c.encode_utf8(&mut [0; 6]) as &str)
220 }
221}
222
223impl From<SharedString> for String {
224 fn from(s: SharedString) -> String {
225 s.as_str().into()
226 }
227}
228
229impl From<&SharedString> for String {
230 fn from(s: &SharedString) -> String {
231 s.as_str().into()
232 }
233}
234
235impl core::ops::AddAssign<&str> for SharedString {
236 fn add_assign(&mut self, other: &str) {
237 self.push_str(other);
238 }
239}
240
241impl 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
249impl 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
255impl Write for SharedString {
256 fn write_str(&mut self, s: &str) -> core::fmt::Result {
257 self.push_str(s);
258 Ok(())
259 }
260}
261
262impl 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
269pub 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]
278fn 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]
300fn 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")]
332pub(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]
441fn 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