Skip to main content

i_slint_core/
string.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4// cSpell:ignore xaaaa
5
6//! module for the SharedString and related things
7
8#![allow(unsafe_code)]
9#![warn(missing_docs)]
10
11use crate::SharedVector;
12use alloc::string::String;
13use core::fmt::{Debug, Display, Write};
14use core::ops::Deref;
15#[cfg(not(feature = "std"))]
16#[allow(unused)]
17use num_traits::Float;
18
19/// This macro is the same as [`std::format!`], but it returns a [`SharedString`] instead.
20///
21/// ### Example
22/// ```rust
23/// let s : slint::SharedString = slint::format!("Hello {}", "world");
24/// assert_eq!(s, slint::SharedString::from("Hello world"));
25/// ```
26#[macro_export]
27macro_rules! format {
28    ($($arg:tt)*) => {{
29        $crate::string::format(core::format_args!($($arg)*))
30    }}
31}
32
33/// A string type used by the Slint run-time.
34///
35/// SharedString uses implicit data sharing to make it efficient to pass around copies. When
36/// cloning, a reference to the data is cloned, not the data itself. The data itself is only copied
37/// when modifying it, for example using [push_str](SharedString::push_str). This is also called copy-on-write.
38///
39/// Under the hood the string data is UTF-8 encoded and it is always terminated with a null character.
40///
41/// `SharedString` implements [`Deref<Target=str>`] so it can be easily passed to any function taking a `&str`.
42/// It also implement `From` such that it an easily be converted to and from the typical rust String type with `.into()`
43#[derive(Clone, Default)]
44#[repr(C)]
45pub struct SharedString {
46    // Invariant: valid utf-8, `\0` terminated
47    inner: SharedVector<u8>,
48}
49
50impl SharedString {
51    /// Creates a new empty string
52    ///
53    /// Same as `SharedString::default()`
54    pub fn new() -> Self {
55        Self::default()
56    }
57
58    fn as_ptr(&self) -> *const u8 {
59        self.inner.as_ptr()
60    }
61
62    /// Size of the string, in bytes. This excludes the terminating null character.
63    pub fn len(&self) -> usize {
64        self.inner.len().saturating_sub(1)
65    }
66
67    /// Return true if the String is empty
68    pub fn is_empty(&self) -> bool {
69        self.len() == 0
70    }
71
72    /// Return a slice to the string
73    pub fn as_str(&self) -> &str {
74        // Safety: self.as_ptr is a pointer from the inner which has utf-8
75        unsafe {
76            core::str::from_utf8_unchecked(core::slice::from_raw_parts(self.as_ptr(), self.len()))
77        }
78    }
79
80    /// Replace in this string characters equal to `from` with the character `to` `count` times
81    pub(crate) fn replace_characters(&mut self, from: char, to: char, count: usize) {
82        let mut from_buffer = [0u8; 4];
83        let mut to_buffer = [0u8; 4];
84        self.inner.replace_range(
85            from.encode_utf8(&mut from_buffer).as_bytes(),
86            to.encode_utf8(&mut to_buffer).as_bytes(),
87            count,
88        );
89    }
90
91    /// Append a string to this string
92    ///
93    /// ```
94    /// # use i_slint_core::SharedString;
95    /// let mut hello = SharedString::from("Hello");
96    /// hello.push_str(", ");
97    /// hello.push_str("World");
98    /// hello.push_str("!");
99    /// assert_eq!(hello, "Hello, World!");
100    /// ```
101    pub fn push_str(&mut self, x: &str) {
102        let mut iter = x.as_bytes().iter().copied();
103        if self.inner.is_empty() {
104            self.inner.extend(iter.chain(core::iter::once(0)));
105        } else if let Some(first) = iter.next() {
106            // We skip the `first` from `iter` because we will write it at the
107            // location of the previous `\0`, after extend did the re-alloc of the
108            // right size
109            let prev_len = self.len();
110            self.inner.extend(iter.chain(core::iter::once(0)));
111            self.inner.make_mut_slice()[prev_len] = first;
112        }
113    }
114}
115
116impl Deref for SharedString {
117    type Target = str;
118    fn deref(&self) -> &Self::Target {
119        self.as_str()
120    }
121}
122
123impl From<&str> for SharedString {
124    fn from(value: &str) -> Self {
125        SharedString {
126            inner: SharedVector::from_iter(
127                value.as_bytes().iter().cloned().chain(core::iter::once(0)),
128            ),
129        }
130    }
131}
132
133impl Debug for SharedString {
134    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
135        Debug::fmt(self.as_str(), f)
136    }
137}
138
139impl Display for SharedString {
140    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
141        Display::fmt(self.as_str(), f)
142    }
143}
144
145impl AsRef<str> for SharedString {
146    #[inline]
147    fn as_ref(&self) -> &str {
148        self.as_str()
149    }
150}
151
152#[cfg(feature = "serde")]
153impl serde::Serialize for SharedString {
154    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
155    where
156        S: serde::Serializer,
157    {
158        let string = self.as_str();
159        serializer.serialize_str(string)
160    }
161}
162
163#[cfg(feature = "serde")]
164impl<'de> serde::Deserialize<'de> for SharedString {
165    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
166    where
167        D: serde::Deserializer<'de>,
168    {
169        let string: alloc::borrow::Cow<str> = serde::Deserialize::deserialize(deserializer)?;
170        Ok(SharedString::from(string.as_ref()))
171    }
172}
173
174#[cfg(feature = "std")]
175impl AsRef<std::ffi::CStr> for SharedString {
176    #[inline]
177    fn as_ref(&self) -> &std::ffi::CStr {
178        if self.inner.is_empty() {
179            return Default::default();
180        }
181        // Safety: we ensure that there is always a terminated \0
182        debug_assert_eq!(self.inner.as_slice()[self.inner.len() - 1], 0);
183        unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked(self.inner.as_slice()) }
184    }
185}
186
187#[cfg(feature = "std")]
188impl AsRef<std::path::Path> for SharedString {
189    #[inline]
190    fn as_ref(&self) -> &std::path::Path {
191        self.as_str().as_ref()
192    }
193}
194
195#[cfg(feature = "std")]
196impl AsRef<std::ffi::OsStr> for SharedString {
197    #[inline]
198    fn as_ref(&self) -> &std::ffi::OsStr {
199        self.as_str().as_ref()
200    }
201}
202
203impl AsRef<[u8]> for SharedString {
204    #[inline]
205    fn as_ref(&self) -> &[u8] {
206        self.as_str().as_bytes()
207    }
208}
209
210impl<T> PartialEq<T> for SharedString
211where
212    T: ?Sized + AsRef<str>,
213{
214    fn eq(&self, other: &T) -> bool {
215        self.as_str() == other.as_ref()
216    }
217}
218impl Eq for SharedString {}
219
220impl<T> PartialOrd<T> for SharedString
221where
222    T: ?Sized + AsRef<str>,
223{
224    fn partial_cmp(&self, other: &T) -> Option<core::cmp::Ordering> {
225        PartialOrd::partial_cmp(self.as_str(), other.as_ref())
226    }
227}
228impl Ord for SharedString {
229    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
230        Ord::cmp(self.as_str(), other.as_str())
231    }
232}
233
234impl From<String> for SharedString {
235    fn from(s: String) -> Self {
236        s.as_str().into()
237    }
238}
239
240impl From<&String> for SharedString {
241    fn from(s: &String) -> Self {
242        s.as_str().into()
243    }
244}
245
246impl From<char> for SharedString {
247    fn from(c: char) -> Self {
248        SharedString::from(c.encode_utf8(&mut [0; 6]) as &str)
249    }
250}
251
252impl From<SharedString> for String {
253    fn from(s: SharedString) -> String {
254        s.as_str().into()
255    }
256}
257
258impl From<&SharedString> for String {
259    fn from(s: &SharedString) -> String {
260        s.as_str().into()
261    }
262}
263
264impl core::ops::AddAssign<&str> for SharedString {
265    fn add_assign(&mut self, other: &str) {
266        self.push_str(other);
267    }
268}
269
270impl core::ops::Add<&str> for SharedString {
271    type Output = SharedString;
272    fn add(mut self, other: &str) -> SharedString {
273        self.push_str(other);
274        self
275    }
276}
277
278impl core::hash::Hash for SharedString {
279    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
280        self.as_str().hash(state)
281    }
282}
283
284impl Write for SharedString {
285    fn write_str(&mut self, s: &str) -> core::fmt::Result {
286        self.push_str(s);
287        Ok(())
288    }
289}
290
291impl core::borrow::Borrow<str> for SharedString {
292    fn borrow(&self) -> &str {
293        self.as_str()
294    }
295}
296
297impl Extend<char> for SharedString {
298    fn extend<X: IntoIterator<Item = char>>(&mut self, iter: X) {
299        let iter = iter.into_iter();
300        self.inner.reserve(iter.size_hint().0);
301        let mut buf = [0; 4];
302        for ch in iter {
303            let utf8 = ch.encode_utf8(&mut buf);
304            self.push_str(utf8);
305        }
306    }
307}
308
309impl FromIterator<char> for SharedString {
310    fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
311        let mut str = Self::new();
312        str.extend(iter);
313        str
314    }
315}
316
317/// Same as [`std::fmt::format()`], but return a [`SharedString`] instead
318pub fn format(args: core::fmt::Arguments<'_>) -> SharedString {
319    // unfortunately, the estimated_capacity is unstable
320    //let capacity = args.estimated_capacity();
321    let mut output = SharedString::default();
322    output.write_fmt(args).unwrap();
323    output
324}
325
326/// A trait for converting a value to a [`SharedString`].
327///
328/// This trait is automatically implemented for any type which implements the [`Display`] trait as long as the trait is in scope.
329/// As such, `ToSharedString` shouldn’t be implemented directly: [`Display`] should be implemented instead, and you get the `ToSharedString` implementation for free.
330pub trait ToSharedString {
331    /// Converts the given value to a [`SharedString`].
332    fn to_shared_string(&self) -> SharedString;
333}
334
335impl<T> ToSharedString for T
336where
337    T: Display + ?Sized,
338{
339    fn to_shared_string(&self) -> SharedString {
340        format!("{}", self)
341    }
342}
343
344/// Convert a f64 to a SharedString but unlocalized with "." as decimal separator
345#[inline]
346pub fn shared_string_from_number_unlocalized(n: f64) -> SharedString {
347    crate::format!("{}", i_slint_common::FormattedNumber(n))
348}
349
350/// Convert a f64 to a SharedString
351pub fn shared_string_from_number(n: f64) -> SharedString {
352    crate::context::GLOBAL_CONTEXT.with(|ctx| {
353        let mut result = shared_string_from_number_unlocalized(n);
354
355        if let Some(ctx) = ctx.get() {
356            let pinned = ctx.0.as_ref().project_ref();
357            let decimal_separator = pinned.locale_decimal_separator.get();
358            if decimal_separator != i_slint_common::DEFAULT_DECIMAL_SEPARATOR {
359                result.replace_characters('.', decimal_separator, 1);
360            }
361        }
362        result
363    })
364}
365
366/// Convert a f64 to a SharedString with a fixed number of digits after the decimal point
367pub fn shared_string_from_number_fixed(n: f64, digits: usize) -> SharedString {
368    crate::context::GLOBAL_CONTEXT.with(|ctx| {
369        let mut result = crate::format!("{number:.digits$}", number = n, digits = digits);
370
371        if let Some(ctx) = ctx.get() {
372            let pinned = ctx.0.as_ref().project_ref();
373            let decimal_separator = pinned.locale_decimal_separator.get();
374            if decimal_separator != i_slint_common::DEFAULT_DECIMAL_SEPARATOR {
375                result.replace_characters('.', decimal_separator, 1);
376            }
377        }
378        result
379    })
380}
381
382/// Convert a f64 to a SharedString following a similar logic as JavaScript's Number.toPrecision()
383pub fn shared_string_from_number_precision(n: f64, precision: usize) -> SharedString {
384    let exponent = f64::log10(n.abs()).floor() as isize;
385    if precision == 0 {
386        shared_string_from_number(n)
387    } else if exponent < -6 || (exponent >= 0 && exponent as usize >= precision) {
388        crate::format!(
389            "{number:.digits$e}",
390            number = n,
391            digits = precision.saturating_add_signed(-1)
392        )
393    } else {
394        shared_string_from_number_fixed(n, precision.saturating_add_signed(-(exponent + 1)))
395    }
396}
397
398/// Convert a string to a float
399pub fn string_to_float(string: &str) -> Option<f32> {
400    crate::context::GLOBAL_CONTEXT.with(|ctx| {
401        let sep = ctx.get().map(|ctx| ctx.locale_decimal_separator()).unwrap_or('.');
402
403        if sep == '.' {
404            string.parse::<f32>().ok()
405        } else {
406            if string.contains('.') {
407                return None;
408            }
409            // Normalize locale separator to '.' because f64::parse only accepts '.'
410            string.replace(sep, ".").parse::<f32>().ok()
411        }
412    })
413}
414
415#[test]
416fn test_string_to_float() {
417    const TEST: &[(&str, Option<f32>)] = &[
418        ("-", None),
419        (".", None),
420        ("-.", None),
421        ("-.5", Some(-0.5)),
422        ("--", None),
423        ("..", None),
424        ("5.5.", None),
425        ("231.435", Some(231.435)),
426        ("-0.007", Some(-0.007)),
427        ("10e6", Some(10e6)),
428    ];
429
430    for (test_string, result) in TEST {
431        assert_eq!(string_to_float(test_string), *result);
432    }
433}
434
435#[test]
436fn simple_test() {
437    use std::string::ToString;
438    let x = SharedString::from("hello world!");
439    assert_eq!(x, "hello world!");
440    assert_ne!(x, "hello world?");
441    assert_eq!(x, x.clone());
442    assert_eq!("hello world!", x.as_str());
443    let string = String::from("hello world!");
444    assert_eq!(x, string);
445    assert_eq!(x.to_string(), string);
446    let def = SharedString::default();
447    assert_eq!(def, SharedString::default());
448    assert_eq!(def, SharedString::new());
449    assert_ne!(def, x);
450    assert_eq!(
451        (&x as &dyn AsRef<std::ffi::CStr>).as_ref(),
452        &*std::ffi::CString::new("hello world!").unwrap()
453    );
454    assert_eq!(SharedString::from('h'), "h");
455    assert_eq!(SharedString::from('😎'), "😎");
456}
457
458#[test]
459fn threading() {
460    let shared_cst = SharedString::from("Hello there!");
461    let shared_mtx = std::sync::Arc::new(std::sync::Mutex::new(SharedString::from("Shared:")));
462    let mut handles = std::vec::Vec::new();
463    for _ in 0..20 {
464        let cst = shared_cst.clone();
465        let mtx = shared_mtx.clone();
466        handles.push(std::thread::spawn(move || {
467            assert_eq!(cst, "Hello there!");
468            let mut cst2 = cst.clone();
469            cst2.push_str(" ... or not?");
470            assert_eq!(cst2, "Hello there! ... or not?");
471            assert_eq!(cst.clone(), "Hello there!");
472
473            let shared = {
474                let mut lock = mtx.lock().unwrap();
475                assert!(lock.starts_with("Shared:"));
476                lock.push_str("!");
477                lock.clone()
478            };
479            assert!(shared.clone().starts_with("Shared:"));
480        }));
481    }
482    for j in handles {
483        j.join().unwrap();
484    }
485    assert_eq!(shared_cst.clone(), "Hello there!");
486    assert_eq!(shared_mtx.lock().unwrap().as_str(), "Shared:!!!!!!!!!!!!!!!!!!!!");
487    // 20x"!"
488}
489
490#[test]
491fn to_shared_string() {
492    let i = 5.1;
493    let five = SharedString::from("5.1");
494
495    assert_eq!(five, i.to_shared_string());
496}
497
498#[test]
499fn test_replace_characters() {
500    let mut value = SharedString::from("5.1");
501    value.replace_characters('.', ',', 1);
502
503    assert_eq!(value, "5,1".to_shared_string());
504}
505
506#[cfg(feature = "ffi")]
507pub(crate) mod ffi {
508    use super::*;
509
510    /// for cbindgen.
511    #[allow(non_camel_case_types)]
512    type c_char = u8;
513
514    #[unsafe(no_mangle)]
515    /// Returns a nul-terminated pointer for this string.
516    /// The returned value is owned by the string, and should not be used after any
517    /// mutable function have been called on the string, and must not be freed.
518    pub extern "C" fn slint_shared_string_bytes(ss: &SharedString) -> *const c_char {
519        if ss.is_empty() { c"".as_ptr().cast() } else { ss.as_ptr() }
520    }
521
522    #[unsafe(no_mangle)]
523    /// Destroy the shared string
524    pub unsafe extern "C" fn slint_shared_string_drop(ss: *const SharedString) {
525        unsafe {
526            core::ptr::read(ss);
527        }
528    }
529
530    #[unsafe(no_mangle)]
531    /// Increment the reference count of the string.
532    /// The resulting structure must be passed to slint_shared_string_drop
533    pub unsafe extern "C" fn slint_shared_string_clone(out: *mut SharedString, ss: &SharedString) {
534        unsafe { core::ptr::write(out, ss.clone()) }
535    }
536
537    #[unsafe(no_mangle)]
538    /// Safety: bytes must be a valid utf-8 string of size len without null inside.
539    /// The resulting structure must be passed to slint_shared_string_drop
540    pub unsafe extern "C" fn slint_shared_string_from_bytes(
541        out: *mut SharedString,
542        bytes: *const c_char,
543        len: usize,
544    ) {
545        unsafe {
546            let str = core::str::from_utf8(core::slice::from_raw_parts(bytes, len)).unwrap();
547            core::ptr::write(out, SharedString::from(str));
548        }
549    }
550
551    /// Create a string from a number but unlocalized.
552    /// The resulting structure must be passed to slint_shared_string_drop
553    #[unsafe(no_mangle)]
554    pub unsafe extern "C" fn slint_shared_string_from_number_unlocalized(
555        out: *mut SharedString,
556        n: f64,
557    ) {
558        let str = shared_string_from_number_unlocalized(n);
559        unsafe { core::ptr::write(out, str) };
560    }
561
562    /// Create a string from a number.
563    /// The resulting structure must be passed to slint_shared_string_drop
564    #[unsafe(no_mangle)]
565    pub unsafe extern "C" fn slint_shared_string_from_number(out: *mut SharedString, n: f64) {
566        let str = shared_string_from_number(n);
567        unsafe { core::ptr::write(out, str) };
568    }
569
570    #[test]
571    fn test_slint_shared_string_from_number() {
572        unsafe {
573            let mut s = core::mem::MaybeUninit::uninit();
574            slint_shared_string_from_number(s.as_mut_ptr(), 45.);
575            assert_eq!(s.assume_init(), "45");
576
577            let mut s = core::mem::MaybeUninit::uninit();
578            slint_shared_string_from_number(s.as_mut_ptr(), 45.12);
579            assert_eq!(s.assume_init(), "45.12");
580
581            let mut s = core::mem::MaybeUninit::uninit();
582            slint_shared_string_from_number(s.as_mut_ptr(), -1325466.);
583            assert_eq!(s.assume_init(), "-1325466");
584
585            let mut s = core::mem::MaybeUninit::uninit();
586            slint_shared_string_from_number(s.as_mut_ptr(), 0.);
587            assert_eq!(s.assume_init(), "0");
588
589            let mut s = core::mem::MaybeUninit::uninit();
590            slint_shared_string_from_number(
591                s.as_mut_ptr(),
592                ((1235.82756f32 * 1000f32).round() / 1000f32) as _,
593            );
594            assert_eq!(s.assume_init(), "1235.828");
595        }
596    }
597
598    #[unsafe(no_mangle)]
599    pub extern "C" fn slint_shared_string_from_number_fixed(
600        out: &mut SharedString,
601        n: f64,
602        digits: usize,
603    ) {
604        *out = shared_string_from_number_fixed(n, digits);
605    }
606
607    #[test]
608    fn test_slint_shared_string_from_number_fixed() {
609        let mut s = SharedString::default();
610
611        let num = 12345.6789;
612
613        slint_shared_string_from_number_fixed(&mut s, num, 0);
614        assert_eq!(s.as_str(), "12346");
615
616        slint_shared_string_from_number_fixed(&mut s, num, 1);
617        assert_eq!(s.as_str(), "12345.7");
618
619        slint_shared_string_from_number_fixed(&mut s, num, 6);
620        assert_eq!(s.as_str(), "12345.678900");
621
622        let num = -12345.6789;
623
624        slint_shared_string_from_number_fixed(&mut s, num, 0);
625        assert_eq!(s.as_str(), "-12346");
626
627        slint_shared_string_from_number_fixed(&mut s, num, 1);
628        assert_eq!(s.as_str(), "-12345.7");
629
630        slint_shared_string_from_number_fixed(&mut s, num, 6);
631        assert_eq!(s.as_str(), "-12345.678900");
632
633        slint_shared_string_from_number_fixed(&mut s, 1.23E+20_f64, 2);
634        assert_eq!(s.as_str(), "123000000000000000000.00");
635
636        slint_shared_string_from_number_fixed(&mut s, 1.23E-10_f64, 2);
637        assert_eq!(s.as_str(), "0.00");
638
639        slint_shared_string_from_number_fixed(&mut s, 2.34, 1);
640        assert_eq!(s.as_str(), "2.3");
641
642        slint_shared_string_from_number_fixed(&mut s, 2.35, 1);
643        assert_eq!(s.as_str(), "2.4");
644
645        slint_shared_string_from_number_fixed(&mut s, 2.55, 1);
646        assert_eq!(s.as_str(), "2.5");
647    }
648
649    #[unsafe(no_mangle)]
650    pub extern "C" fn slint_shared_string_from_number_precision(
651        out: &mut SharedString,
652        n: f64,
653        precision: usize,
654    ) {
655        *out = shared_string_from_number_precision(n, precision);
656    }
657
658    #[test]
659    fn test_slint_shared_string_from_number_precision() {
660        let mut s = SharedString::default();
661
662        let num = 5.123456;
663
664        slint_shared_string_from_number_precision(&mut s, num, 0);
665        assert_eq!(s.as_str(), "5.123456");
666
667        slint_shared_string_from_number_precision(&mut s, num, 5);
668        assert_eq!(s.as_str(), "5.1235");
669
670        slint_shared_string_from_number_precision(&mut s, num, 2);
671        assert_eq!(s.as_str(), "5.1");
672
673        slint_shared_string_from_number_precision(&mut s, num, 1);
674        assert_eq!(s.as_str(), "5");
675
676        let num = 0.000123;
677
678        slint_shared_string_from_number_precision(&mut s, num, 0);
679        assert_eq!(s.as_str(), "0.000123");
680
681        slint_shared_string_from_number_precision(&mut s, num, 5);
682        assert_eq!(s.as_str(), "0.00012300");
683
684        slint_shared_string_from_number_precision(&mut s, num, 2);
685        assert_eq!(s.as_str(), "0.00012");
686
687        slint_shared_string_from_number_precision(&mut s, num, 1);
688        assert_eq!(s.as_str(), "0.0001");
689
690        let num = 1234.5;
691
692        slint_shared_string_from_number_precision(&mut s, num, 1);
693        assert_eq!(s.as_str(), "1e3");
694
695        slint_shared_string_from_number_precision(&mut s, num, 2);
696        assert_eq!(s.as_str(), "1.2e3");
697
698        slint_shared_string_from_number_precision(&mut s, num, 6);
699        assert_eq!(s.as_str(), "1234.50");
700
701        let num = -1234.5;
702
703        slint_shared_string_from_number_precision(&mut s, num, 1);
704        assert_eq!(s.as_str(), "-1e3");
705
706        slint_shared_string_from_number_precision(&mut s, num, 2);
707        assert_eq!(s.as_str(), "-1.2e3");
708
709        slint_shared_string_from_number_precision(&mut s, num, 6);
710        assert_eq!(s.as_str(), "-1234.50");
711
712        let num = 0.00000012345;
713
714        slint_shared_string_from_number_precision(&mut s, num, 1);
715        assert_eq!(s.as_str(), "1e-7");
716
717        slint_shared_string_from_number_precision(&mut s, num, 10);
718        assert_eq!(s.as_str(), "1.234500000e-7");
719    }
720
721    /// Append some bytes to an existing shared string
722    ///
723    /// bytes must be a valid utf8 array of size `len`, without null bytes inside
724    #[unsafe(no_mangle)]
725    pub unsafe extern "C" fn slint_shared_string_append(
726        self_: &mut SharedString,
727        bytes: *const c_char,
728        len: usize,
729    ) {
730        let str = core::str::from_utf8(unsafe { core::slice::from_raw_parts(bytes, len) }).unwrap();
731        self_.push_str(str);
732    }
733    #[test]
734    fn test_slint_shared_string_append() {
735        let mut s = SharedString::default();
736        let mut append = |x: &str| unsafe {
737            slint_shared_string_append(&mut s, x.as_bytes().as_ptr(), x.len());
738        };
739        append("Hello");
740        append(", ");
741        append("world");
742        append("");
743        append("!");
744        assert_eq!(s.as_str(), "Hello, world!");
745    }
746
747    #[unsafe(no_mangle)]
748    pub unsafe extern "C" fn slint_shared_string_to_lowercase(
749        out: &mut SharedString,
750        ss: &SharedString,
751    ) {
752        *out = SharedString::from(ss.to_lowercase());
753    }
754    #[test]
755    fn test_slint_shared_string_to_lowercase() {
756        let s = SharedString::from("Hello");
757        let mut out = SharedString::default();
758
759        unsafe {
760            slint_shared_string_to_lowercase(&mut out, &s);
761        }
762        assert_eq!(out.as_str(), "hello");
763    }
764
765    #[unsafe(no_mangle)]
766    pub unsafe extern "C" fn slint_shared_string_to_uppercase(
767        out: &mut SharedString,
768        ss: &SharedString,
769    ) {
770        *out = SharedString::from(ss.to_uppercase());
771    }
772    #[test]
773    fn test_slint_shared_string_to_uppercase() {
774        let s = SharedString::from("Hello");
775        let mut out = SharedString::default();
776
777        unsafe {
778            slint_shared_string_to_uppercase(&mut out, &s);
779        }
780        assert_eq!(out.as_str(), "HELLO");
781    }
782}
783
784#[cfg(feature = "serde")]
785#[test]
786fn test_serialize_deserialize_sharedstring() {
787    let v = SharedString::from("data");
788    let serialized = serde_json::to_string(&v).unwrap();
789    let deserialized: SharedString = serde_json::from_str(&serialized).unwrap();
790    assert_eq!(v, deserialized);
791}
792
793#[cfg(feature = "serde")]
794#[test]
795fn test_serialize_deserialize_sharedstring_from_reader() {
796    let v = SharedString::from("data");
797    let serialized = serde_json::to_string(&v).unwrap();
798    let deserialized: SharedString = serde_json::from_reader(serialized.as_bytes()).unwrap();
799    assert_eq!(v, deserialized);
800}
801
802#[test]
803fn test_extend_from_chars() {
804    let mut s = SharedString::from("x");
805    s.extend(core::iter::repeat_n('a', 4).chain(core::iter::once('🍌')));
806    assert_eq!(s.as_str(), "xaaaa🍌");
807}
808
809#[test]
810fn test_collect_from_chars() {
811    let s: SharedString = core::iter::repeat_n('a', 4).chain(core::iter::once('🍌')).collect();
812    assert_eq!(s.as_str(), "aaaa🍌");
813}