From b4fed2546a97e87e4e0da1e3e1f5430f053e2314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 8 Feb 2024 10:34:17 +0100 Subject: [PATCH 01/11] String: add StringView --- src/string.rs | 433 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 428 insertions(+), 5 deletions(-) diff --git a/src/string.rs b/src/string.rs index dce6394d2d..2aa60042d0 100644 --- a/src/string.rs +++ b/src/string.rs @@ -9,7 +9,7 @@ use core::{ str::{self, Utf8Error}, }; -use crate::Vec; +use crate::{Vec, VecView}; /// A possible error value when converting a [`String`] from a UTF-16 byte slice. /// @@ -33,11 +33,46 @@ impl fmt::Display for FromUtf16Error { } } -/// A fixed capacity [`String`](https://doc.rust-lang.org/std/string/struct.String.html). -pub struct String { - vec: Vec, +mod sealed { + ///
This is private API and should not be used
+ pub struct StringInner { + pub(super) vec: V, + } } +// Workaround https://github.com/rust-lang/rust/issues/119015. This is required so that the methods on `VecView` and `Vec` are properly documented. +// cfg(doc) prevents `StringInner` being part of the public API. +// doc(hidden) prevents the `pub use sealed::StringInner` from being visible in the documentation. +#[cfg(doc)] +#[doc(hidden)] +pub use sealed::StringInner as _; + +/// A fixed capacity [`String`](https://doc.rust-lang.org/std/string/struct.String.html). +pub type String = sealed::StringInner>; + +/// A [`String`] with dynamic capacity +/// +/// [`String`] coerces to `StringView`. `StringView` is `!Sized`, meaning it can only ever be used by reference. +/// +/// Unlike [`String`], `StringView` does not have an `N` const-generic parameter. +/// This has the ergonomic advantage of making it possible to use functions without needing to know at +/// compile-time the size of the buffers used, for example for use in `dyn` traits. +/// +/// `StringView` is to `String` what [`VecView`] is to [`Vec`]. +/// +/// ```rust +/// use heapless::string::{String, StringView}; +/// +/// let mut s: String<12> = String::try_from("Hello").unwrap(); +/// let view: &StringView = &s; +/// assert_eq!(view, "Hello"); +/// +/// let mut_view: &mut StringView = &mut s; +/// mut_view.push_str(" World!"); +/// assert_eq!(s, "Hello World!"); +/// ``` +pub type StringView = sealed::StringInner>; + impl String { /// Constructs a new, empty `String` with a fixed capacity of `N` bytes. /// @@ -458,6 +493,284 @@ impl String { } } +impl StringView { + /// Extracts a string slice containing the entire string. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use heapless::String; + /// + /// let mut s: String<4> = String::try_from("ab")?; + /// assert!(s.as_str() == "ab"); + /// + /// let _s = s.as_str(); + /// // s.push('c'); // <- cannot borrow `s` as mutable because it is also borrowed as immutable + /// # Ok::<(), ()>(()) + /// ``` + #[inline] + pub fn as_str(&self) -> &str { + unsafe { str::from_utf8_unchecked(self.vec.as_slice()) } + } + + /// Converts a `String` into a mutable string slice. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use heapless::String; + /// + /// let mut s: String<4> = String::try_from("ab")?; + /// let s = s.as_mut_str(); + /// s.make_ascii_uppercase(); + /// # Ok::<(), ()>(()) + /// ``` + #[inline] + pub fn as_mut_str(&mut self) -> &mut str { + unsafe { str::from_utf8_unchecked_mut(self.vec.as_mut_slice()) } + } + + /// Returns a mutable reference to the contents of this `String`. + /// + /// # Safety + /// + /// This function is unsafe because it does not check that the bytes passed + /// to it are valid UTF-8. If this constraint is violated, it may cause + /// memory unsafety issues with future users of the `String`, as the rest of + /// the library assumes that `String`s are valid UTF-8. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use heapless::String; + /// + /// let mut s: String<8> = String::try_from("hello")?; + /// + /// unsafe { + /// let vec = s.as_mut_vec(); + /// assert_eq!(&[104, 101, 108, 108, 111][..], &vec[..]); + /// + /// vec.reverse(); + /// } + /// assert_eq!(s, "olleh"); + /// # Ok::<(), ()>(()) + /// ``` + pub unsafe fn as_mut_vec_view(&mut self) -> &mut VecView { + &mut self.vec + } + + /// Appends a given string slice onto the end of this `String`. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use heapless::String; + /// + /// let mut s: String<8> = String::try_from("foo")?; + /// + /// assert!(s.push_str("bar").is_ok()); + /// + /// assert_eq!("foobar", s); + /// + /// assert!(s.push_str("tender").is_err()); + /// # Ok::<(), ()>(()) + /// ``` + #[inline] + #[allow(clippy::result_unit_err)] + pub fn push_str(&mut self, string: &str) -> Result<(), ()> { + self.vec.extend_from_slice(string.as_bytes()) + } + + /// Returns the maximum number of elements the String can hold. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use heapless::String; + /// + /// let mut s: String<4> = String::new(); + /// assert!(s.capacity() == 4); + /// ``` + #[inline] + pub fn capacity(&self) -> usize { + self.vec.capacity() + } + + /// Appends the given [`char`] to the end of this `String`. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use heapless::String; + /// + /// let mut s: String<8> = String::try_from("abc")?; + /// + /// s.push('1').unwrap(); + /// s.push('2').unwrap(); + /// s.push('3').unwrap(); + /// + /// assert!("abc123" == s.as_str()); + /// + /// assert_eq!("abc123", s); + /// # Ok::<(), ()>(()) + /// ``` + #[inline] + #[allow(clippy::result_unit_err)] + pub fn push(&mut self, c: char) -> Result<(), ()> { + match c.len_utf8() { + 1 => self.vec.push(c as u8).map_err(|_| {}), + _ => self + .vec + .extend_from_slice(c.encode_utf8(&mut [0; 4]).as_bytes()), + } + } + + /// Shortens this `String` to the specified length. + /// + /// If `new_len` is greater than the string's current length, this has no + /// effect. + /// + /// Note that this method has no effect on the allocated capacity + /// of the string + /// + /// # Panics + /// + /// Panics if `new_len` does not lie on a [`char`] boundary. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use heapless::String; + /// + /// let mut s: String<8> = String::try_from("hello")?; + /// + /// s.truncate(2); + /// + /// assert_eq!("he", s); + /// # Ok::<(), ()>(()) + /// ``` + #[inline] + pub fn truncate(&mut self, new_len: usize) { + if new_len <= self.len() { + assert!(self.is_char_boundary(new_len)); + self.vec.truncate(new_len) + } + } + + /// Removes the last character from the string buffer and returns it. + /// + /// Returns [`None`] if this `String` is empty. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use heapless::String; + /// + /// let mut s: String<8> = String::try_from("foo")?; + /// + /// assert_eq!(s.pop(), Some('o')); + /// assert_eq!(s.pop(), Some('o')); + /// assert_eq!(s.pop(), Some('f')); + /// + /// assert_eq!(s.pop(), None); + /// Ok::<(), ()>(()) + /// ``` + pub fn pop(&mut self) -> Option { + let ch = self.chars().next_back()?; + + // pop bytes that correspond to `ch` + for _ in 0..ch.len_utf8() { + unsafe { + self.vec.pop_unchecked(); + } + } + + Some(ch) + } + + /// Removes a [`char`] from this `String` at a byte position and returns it. + /// + /// Note: Because this shifts over the remaining elements, it has a + /// worst-case performance of *O*(n). + /// + /// # Panics + /// + /// Panics if `idx` is larger than or equal to the `String`'s length, + /// or if it does not lie on a [`char`] boundary. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use heapless::String; + /// + /// let mut s: String<8> = String::try_from("foo").unwrap(); + /// + /// assert_eq!(s.remove(0), 'f'); + /// assert_eq!(s.remove(1), 'o'); + /// assert_eq!(s.remove(0), 'o'); + /// ``` + #[inline] + pub fn remove(&mut self, index: usize) -> char { + let ch = match self[index..].chars().next() { + Some(ch) => ch, + None => panic!("cannot remove a char from the end of a string"), + }; + + let next = index + ch.len_utf8(); + let len = self.len(); + let ptr = self.vec.as_mut_ptr(); + unsafe { + core::ptr::copy(ptr.add(next), ptr.add(index), len - next); + self.vec.set_len(len - (next - index)); + } + ch + } + + /// Truncates this `String`, removing all contents. + /// + /// While this means the `String` will have a length of zero, it does not + /// touch its capacity. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use heapless::String; + /// + /// let mut s: String<8> = String::try_from("foo")?; + /// + /// s.clear(); + /// + /// assert!(s.is_empty()); + /// assert_eq!(0, s.len()); + /// assert_eq!(8, s.capacity()); + /// Ok::<(), ()>(()) + /// ``` + #[inline] + pub fn clear(&mut self) { + self.vec.clear() + } +} + impl Default for String { fn default() -> Self { Self::new() @@ -527,12 +840,24 @@ impl fmt::Debug for String { } } +impl fmt::Debug for StringView { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + ::fmt(self, f) + } +} + impl fmt::Display for String { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ::fmt(self, f) } } +impl fmt::Display for StringView { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + ::fmt(self, f) + } +} + impl hash::Hash for String { #[inline] fn hash(&self, hasher: &mut H) { @@ -540,6 +865,13 @@ impl hash::Hash for String { } } +impl hash::Hash for StringView { + #[inline] + fn hash(&self, hasher: &mut H) { + ::hash(self, hasher) + } +} + impl fmt::Write for String { fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { self.push_str(s).map_err(|_| fmt::Error) @@ -550,6 +882,16 @@ impl fmt::Write for String { } } +impl fmt::Write for StringView { + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + self.push_str(s).map_err(|_| fmt::Error) + } + + fn write_char(&mut self, c: char) -> Result<(), fmt::Error> { + self.push(c).map_err(|_| fmt::Error) + } +} + impl ops::Deref for String { type Target = str; @@ -558,12 +900,26 @@ impl ops::Deref for String { } } +impl ops::Deref for StringView { + type Target = str; + + fn deref(&self) -> &str { + self.as_str() + } +} + impl ops::DerefMut for String { fn deref_mut(&mut self) -> &mut str { self.as_mut_str() } } +impl ops::DerefMut for StringView { + fn deref_mut(&mut self) -> &mut str { + self.as_mut_str() + } +} + impl AsRef for String { #[inline] fn as_ref(&self) -> &str { @@ -571,6 +927,13 @@ impl AsRef for String { } } +impl AsRef for StringView { + #[inline] + fn as_ref(&self) -> &str { + self + } +} + impl AsRef<[u8]> for String { #[inline] fn as_ref(&self) -> &[u8] { @@ -578,12 +941,25 @@ impl AsRef<[u8]> for String { } } +impl AsRef<[u8]> for StringView { + #[inline] + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + impl PartialEq> for String { fn eq(&self, rhs: &String) -> bool { str::eq(&**self, &**rhs) } } +impl PartialEq for StringView { + fn eq(&self, rhs: &StringView) -> bool { + str::eq(&**self, &**rhs) + } +} + // String == str impl PartialEq for String { #[inline] @@ -592,6 +968,14 @@ impl PartialEq for String { } } +// StringView == str +impl PartialEq for StringView { + #[inline] + fn eq(&self, other: &str) -> bool { + str::eq(self, other) + } +} + // String == &'str impl PartialEq<&str> for String { #[inline] @@ -600,6 +984,14 @@ impl PartialEq<&str> for String { } } +// StringView == &'str +impl PartialEq<&str> for StringView { + #[inline] + fn eq(&self, other: &&str) -> bool { + str::eq(self, &other[..]) + } +} + // str == String impl PartialEq> for str { #[inline] @@ -608,6 +1000,14 @@ impl PartialEq> for str { } } +// str == StringView +impl PartialEq for str { + #[inline] + fn eq(&self, other: &StringView) -> bool { + str::eq(self, &other[..]) + } +} + // &'str == String impl PartialEq> for &str { #[inline] @@ -616,12 +1016,28 @@ impl PartialEq> for &str { } } +// &'str == StringView +impl PartialEq for &str { + #[inline] + fn eq(&self, other: &StringView) -> bool { + str::eq(self, &other[..]) + } +} + impl Eq for String {} +impl Eq for StringView {} impl PartialOrd> for String { #[inline] fn partial_cmp(&self, other: &String) -> Option { - PartialOrd::partial_cmp(&**self, &**other) + Some(Ord::cmp(&**self, &**other)) + } +} + +impl PartialOrd for StringView { + #[inline] + fn partial_cmp(&self, other: &StringView) -> Option { + Some(Ord::cmp(&**self, &**other)) } } @@ -632,6 +1048,13 @@ impl Ord for String { } } +impl Ord for StringView { + #[inline] + fn cmp(&self, other: &Self) -> Ordering { + Ord::cmp(&**self, &**other) + } +} + /// Equivalent to [`format`](https://doc.rust-lang.org/std/fmt/fn.format.html). /// /// Please note that using [`format!`] might be preferable. From 710931520fd58858e2f818930cce8c830ef58737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 8 Feb 2024 10:41:01 +0100 Subject: [PATCH 02/11] Add as_(mut_)view methods --- src/string.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/string.rs b/src/string.rs index 2aa60042d0..4719e66ab5 100644 --- a/src/string.rs +++ b/src/string.rs @@ -94,6 +94,44 @@ impl String { Self { vec: Vec::new() } } + /// Get a reference to the `String`, erasing the `N` const-generic. + /// + /// ```rust + /// # use heapless::string::{String, StringView}; + /// let s: String<12> = String::try_from("Hello").unwrap(); + /// let view: &StringView = s.as_view(); + /// ``` + /// + /// It is often preferable to do the same through type coerction, since `String` implements `Unsize`: + /// + /// ```rust + /// # use heapless::string::{String, StringView}; + /// let s: String<12> = String::try_from("Hello").unwrap(); + /// let view: &StringView = &s; + /// ``` + pub fn as_view(&self) -> &StringView { + self + } + + /// Get a mutable reference to the `String`, erasing the `N` const-generic. + /// + /// ```rust + /// # use heapless::string::{String, StringView}; + /// let mut s: String<12> = String::try_from("Hello").unwrap(); + /// let view: &mut StringView = s.as_mut_view(); + /// ``` + /// + /// It is often preferable to do the same through type coerction, since `String` implements `Unsize`: + /// + /// ```rust + /// # use heapless::string::{String, StringView}; + /// let mut s: String<12> = String::try_from("Hello").unwrap(); + /// let view: &mut StringView = &mut s; + /// ``` + pub fn as_mut_view(&mut self) -> &mut StringView { + self + } + /// Decodes a UTF-16–encoded slice `v` into a `String`, returning [`Err`] /// if `v` contains any invalid data. /// From 505e2394f7a14cf64f084b0ff25b80547d35fdb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 8 Feb 2024 10:43:37 +0100 Subject: [PATCH 03/11] String: add as_mut_vec_view --- src/string.rs | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/string.rs b/src/string.rs index 4719e66ab5..56544089f9 100644 --- a/src/string.rs +++ b/src/string.rs @@ -314,7 +314,7 @@ impl String { /// /// unsafe { /// let vec = s.as_mut_vec(); - /// assert_eq!(&[104, 101, 108, 108, 111][..], &vec[..]); + /// assert_eq!(&b"hello", &vec); /// /// vec.reverse(); /// } @@ -325,6 +325,37 @@ impl String { &mut self.vec } + /// Returns a mutable reference to the contents of this `String`. + /// + /// # Safety + /// + /// This function is unsafe because it does not check that the bytes passed + /// to it are valid UTF-8. If this constraint is violated, it may cause + /// memory unsafety issues with future users of the `String`, as the rest of + /// the library assumes that `String`s are valid UTF-8. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use heapless::String; + /// + /// let mut s: String<8> = String::try_from("hello")?; + /// + /// unsafe { + /// let vec = s.as_mut_vec_view(); + /// assert_eq!(&b"hello", &vec); + /// + /// vec.reverse(); + /// } + /// assert_eq!(s, "olleh"); + /// # Ok::<(), ()>(()) + /// ``` + pub unsafe fn as_mut_vec_view(&mut self) -> &mut VecView { + &mut self.vec + } + /// Appends a given string slice onto the end of this `String`. /// /// # Examples @@ -592,7 +623,7 @@ impl StringView { /// /// unsafe { /// let vec = s.as_mut_vec(); - /// assert_eq!(&[104, 101, 108, 108, 111][..], &vec[..]); + /// assert_eq!(&b"hello", &vec); /// /// vec.reverse(); /// } From 3471eaa939ea2ee3173e4e4b0cbfc77830022613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 8 Feb 2024 10:49:29 +0100 Subject: [PATCH 04/11] String: delegate implementations to StringView --- src/string.rs | 65 ++++++++++++++------------------------------------- 1 file changed, 18 insertions(+), 47 deletions(-) diff --git a/src/string.rs b/src/string.rs index 56544089f9..8dc176079b 100644 --- a/src/string.rs +++ b/src/string.rs @@ -272,7 +272,7 @@ impl String { /// ``` #[inline] pub fn as_str(&self) -> &str { - unsafe { str::from_utf8_unchecked(self.vec.as_slice()) } + self.as_view().as_str() } /// Converts a `String` into a mutable string slice. @@ -291,7 +291,7 @@ impl String { /// ``` #[inline] pub fn as_mut_str(&mut self) -> &mut str { - unsafe { str::from_utf8_unchecked_mut(self.vec.as_mut_slice()) } + self.as_mut_view().as_mut_str() } /// Returns a mutable reference to the contents of this `String`. @@ -353,7 +353,7 @@ impl String { /// # Ok::<(), ()>(()) /// ``` pub unsafe fn as_mut_vec_view(&mut self) -> &mut VecView { - &mut self.vec + self.as_mut_view().as_mut_vec_view() } /// Appends a given string slice onto the end of this `String`. @@ -377,7 +377,7 @@ impl String { #[inline] #[allow(clippy::result_unit_err)] pub fn push_str(&mut self, string: &str) -> Result<(), ()> { - self.vec.extend_from_slice(string.as_bytes()) + self.as_mut_view().push_str(string) } /// Returns the maximum number of elements the String can hold. @@ -394,7 +394,7 @@ impl String { /// ``` #[inline] pub fn capacity(&self) -> usize { - self.vec.capacity() + self.as_view().capacity() } /// Appends the given [`char`] to the end of this `String`. @@ -420,12 +420,7 @@ impl String { #[inline] #[allow(clippy::result_unit_err)] pub fn push(&mut self, c: char) -> Result<(), ()> { - match c.len_utf8() { - 1 => self.vec.push(c as u8).map_err(|_| {}), - _ => self - .vec - .extend_from_slice(c.encode_utf8(&mut [0; 4]).as_bytes()), - } + self.as_mut_view().push(c) } /// Shortens this `String` to the specified length. @@ -456,10 +451,7 @@ impl String { /// ``` #[inline] pub fn truncate(&mut self, new_len: usize) { - if new_len <= self.len() { - assert!(self.is_char_boundary(new_len)); - self.vec.truncate(new_len) - } + self.as_mut_view().truncate(new_len) } /// Removes the last character from the string buffer and returns it. @@ -483,16 +475,7 @@ impl String { /// Ok::<(), ()>(()) /// ``` pub fn pop(&mut self) -> Option { - let ch = self.chars().next_back()?; - - // pop bytes that correspond to `ch` - for _ in 0..ch.len_utf8() { - unsafe { - self.vec.pop_unchecked(); - } - } - - Some(ch) + self.as_mut_view().pop() } /// Removes a [`char`] from this `String` at a byte position and returns it. @@ -520,19 +503,7 @@ impl String { /// ``` #[inline] pub fn remove(&mut self, index: usize) -> char { - let ch = match self[index..].chars().next() { - Some(ch) => ch, - None => panic!("cannot remove a char from the end of a string"), - }; - - let next = index + ch.len_utf8(); - let len = self.len(); - let ptr = self.vec.as_mut_ptr(); - unsafe { - core::ptr::copy(ptr.add(next), ptr.add(index), len - next); - self.vec.set_len(len - (next - index)); - } - ch + self.as_mut_view().remove(index) } /// Truncates this `String`, removing all contents. @@ -558,7 +529,7 @@ impl String { /// ``` #[inline] pub fn clear(&mut self) { - self.vec.clear() + self.as_mut_view().clear() } } @@ -905,7 +876,7 @@ impl Clone for String { impl fmt::Debug for String { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - ::fmt(self, f) + self.as_view().fmt(f) } } @@ -917,7 +888,7 @@ impl fmt::Debug for StringView { impl fmt::Display for String { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - ::fmt(self, f) + self.as_view().fmt(f) } } @@ -930,7 +901,7 @@ impl fmt::Display for StringView { impl hash::Hash for String { #[inline] fn hash(&self, hasher: &mut H) { - ::hash(self, hasher) + self.as_view().hash(hasher) } } @@ -943,11 +914,11 @@ impl hash::Hash for StringView { impl fmt::Write for String { fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { - self.push_str(s).map_err(|_| fmt::Error) + self.as_mut_view().write_str(s) } fn write_char(&mut self, c: char) -> Result<(), fmt::Error> { - self.push(c).map_err(|_| fmt::Error) + self.as_mut_view().write_char(c) } } @@ -965,7 +936,7 @@ impl ops::Deref for String { type Target = str; fn deref(&self) -> &str { - self.as_str() + self.as_view().deref() } } @@ -979,7 +950,7 @@ impl ops::Deref for StringView { impl ops::DerefMut for String { fn deref_mut(&mut self) -> &mut str { - self.as_mut_str() + self.as_mut_view().deref_mut() } } @@ -1006,7 +977,7 @@ impl AsRef for StringView { impl AsRef<[u8]> for String { #[inline] fn as_ref(&self) -> &[u8] { - self.as_bytes() + self.as_view().as_bytes() } } From c4c36d2d20ec4c6f28b88a9503cf6a8778f9a155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 8 Feb 2024 11:11:09 +0100 Subject: [PATCH 05/11] Update documentation of StringView functions to document StringView --- src/string.rs | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/string.rs b/src/string.rs index 8dc176079b..9c5eaa31bd 100644 --- a/src/string.rs +++ b/src/string.rs @@ -541,9 +541,10 @@ impl StringView { /// Basic usage: /// /// ``` - /// use heapless::String; + /// use heapless::string::{String, StringView}; /// /// let mut s: String<4> = String::try_from("ab")?; + /// let s: &mut StringView = &mut s; /// assert!(s.as_str() == "ab"); /// /// let _s = s.as_str(); @@ -562,9 +563,10 @@ impl StringView { /// Basic usage: /// /// ``` - /// use heapless::String; + /// use heapless::string::{String, StringView}; /// /// let mut s: String<4> = String::try_from("ab")?; + /// let s: &mut StringView = &mut s; /// let s = s.as_mut_str(); /// s.make_ascii_uppercase(); /// # Ok::<(), ()>(()) @@ -588,12 +590,13 @@ impl StringView { /// Basic usage: /// /// ``` - /// use heapless::String; + /// use heapless::string::{String,StringView}; /// /// let mut s: String<8> = String::try_from("hello")?; + /// let s: &mut StringView = &mut s; /// /// unsafe { - /// let vec = s.as_mut_vec(); + /// let vec = s.as_mut_vec_view(); /// assert_eq!(&b"hello", &vec); /// /// vec.reverse(); @@ -612,9 +615,10 @@ impl StringView { /// Basic usage: /// /// ``` - /// use heapless::String; + /// use heapless::string::{String,StringView}; /// /// let mut s: String<8> = String::try_from("foo")?; + /// let s: &mut StringView = &mut s; /// /// assert!(s.push_str("bar").is_ok()); /// @@ -636,9 +640,10 @@ impl StringView { /// Basic usage: /// /// ``` - /// use heapless::String; + /// use heapless::string::{String,StringView}; /// - /// let mut s: String<4> = String::new(); + /// let s: String<4> = String::new(); + /// let s: &StringView = &s; /// assert!(s.capacity() == 4); /// ``` #[inline] @@ -653,9 +658,10 @@ impl StringView { /// Basic usage: /// /// ``` - /// use heapless::String; + /// use heapless::string::{String,StringView}; /// /// let mut s: String<8> = String::try_from("abc")?; + /// let s: &mut StringView = &mut s; /// /// s.push('1').unwrap(); /// s.push('2').unwrap(); @@ -694,9 +700,10 @@ impl StringView { /// Basic usage: /// /// ``` - /// use heapless::String; + /// use heapless::string::{String,StringView}; /// /// let mut s: String<8> = String::try_from("hello")?; + /// let s: &mut StringView = &mut s; /// /// s.truncate(2); /// @@ -720,9 +727,10 @@ impl StringView { /// Basic usage: /// /// ``` - /// use heapless::String; + /// use heapless::string::{String,StringView}; /// /// let mut s: String<8> = String::try_from("foo")?; + /// let s: &mut StringView = &mut s; /// /// assert_eq!(s.pop(), Some('o')); /// assert_eq!(s.pop(), Some('o')); @@ -759,9 +767,10 @@ impl StringView { /// Basic usage: /// /// ``` - /// use heapless::String; + /// use heapless::string::{String,StringView}; /// /// let mut s: String<8> = String::try_from("foo").unwrap(); + /// let s: &mut StringView = &mut s; /// /// assert_eq!(s.remove(0), 'f'); /// assert_eq!(s.remove(1), 'o'); @@ -794,9 +803,10 @@ impl StringView { /// Basic usage: /// /// ``` - /// use heapless::String; + /// use heapless::string::{String, StringView}; /// /// let mut s: String<8> = String::try_from("foo")?; + /// let s: &mut StringView = &mut s; /// /// s.clear(); /// From 8d23e44db85906be93fc32934bcb7f9f544163e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 8 Feb 2024 11:56:10 +0100 Subject: [PATCH 06/11] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bea10baa6..0e3f283e79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added `VecView`, the `!Sized` version of `Vec`. - Added pool implementations for 64-bit architectures. - Added `IntoIterator` implementation for `LinearMap` +- Added `StringView`, the `!Sized` version of `String`. ### Changed From 4bfba99f2025d095dd34ccc27569648db48b0860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 8 Feb 2024 11:57:45 +0100 Subject: [PATCH 07/11] Fix fmt of comments --- src/string.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/string.rs b/src/string.rs index 9c5eaa31bd..afd2edbfda 100644 --- a/src/string.rs +++ b/src/string.rs @@ -107,7 +107,7 @@ impl String { /// ```rust /// # use heapless::string::{String, StringView}; /// let s: String<12> = String::try_from("Hello").unwrap(); - /// let view: &StringView = &s; + /// let view: &StringView = &s; /// ``` pub fn as_view(&self) -> &StringView { self @@ -126,7 +126,7 @@ impl String { /// ```rust /// # use heapless::string::{String, StringView}; /// let mut s: String<12> = String::try_from("Hello").unwrap(); - /// let view: &mut StringView = &mut s; + /// let view: &mut StringView = &mut s; /// ``` pub fn as_mut_view(&mut self) -> &mut StringView { self @@ -590,7 +590,7 @@ impl StringView { /// Basic usage: /// /// ``` - /// use heapless::string::{String,StringView}; + /// use heapless::string::{String, StringView}; /// /// let mut s: String<8> = String::try_from("hello")?; /// let s: &mut StringView = &mut s; @@ -615,7 +615,7 @@ impl StringView { /// Basic usage: /// /// ``` - /// use heapless::string::{String,StringView}; + /// use heapless::string::{String, StringView}; /// /// let mut s: String<8> = String::try_from("foo")?; /// let s: &mut StringView = &mut s; @@ -640,7 +640,7 @@ impl StringView { /// Basic usage: /// /// ``` - /// use heapless::string::{String,StringView}; + /// use heapless::string::{String, StringView}; /// /// let s: String<4> = String::new(); /// let s: &StringView = &s; @@ -658,7 +658,7 @@ impl StringView { /// Basic usage: /// /// ``` - /// use heapless::string::{String,StringView}; + /// use heapless::string::{String, StringView}; /// /// let mut s: String<8> = String::try_from("abc")?; /// let s: &mut StringView = &mut s; @@ -700,7 +700,7 @@ impl StringView { /// Basic usage: /// /// ``` - /// use heapless::string::{String,StringView}; + /// use heapless::string::{String, StringView}; /// /// let mut s: String<8> = String::try_from("hello")?; /// let s: &mut StringView = &mut s; @@ -727,7 +727,7 @@ impl StringView { /// Basic usage: /// /// ``` - /// use heapless::string::{String,StringView}; + /// use heapless::string::{String, StringView}; /// /// let mut s: String<8> = String::try_from("foo")?; /// let s: &mut StringView = &mut s; @@ -767,7 +767,7 @@ impl StringView { /// Basic usage: /// /// ``` - /// use heapless::string::{String,StringView}; + /// use heapless::string::{String, StringView}; /// /// let mut s: String<8> = String::try_from("foo").unwrap(); /// let s: &mut StringView = &mut s; From 50d0631f4c54e4374f55679660c2be51de36819a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 8 Feb 2024 17:19:29 +0100 Subject: [PATCH 08/11] Impl Serialize for StringView --- src/ser.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/ser.rs b/src/ser.rs index f929ba8b12..4b1d7d5e1a 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -1,8 +1,8 @@ use core::hash::{BuildHasher, Hash}; use crate::{ - binary_heap::Kind as BinaryHeapKind, BinaryHeap, Deque, IndexMap, IndexSet, LinearMap, String, - Vec, + binary_heap::Kind as BinaryHeapKind, string::StringView, BinaryHeap, Deque, IndexMap, IndexSet, + LinearMap, String, Vec, }; use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}; @@ -113,6 +113,15 @@ where // String containers +impl Serialize for StringView { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&*self) + } +} + impl Serialize for String { fn serialize(&self, serializer: S) -> Result where From 39da64f47aa5dab1948c9860a202d33b7c7e2029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Fri, 9 Feb 2024 09:16:39 +0100 Subject: [PATCH 09/11] Add inline hint to methods that defer to the view --- src/string.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/string.rs b/src/string.rs index afd2edbfda..cead240d1c 100644 --- a/src/string.rs +++ b/src/string.rs @@ -109,6 +109,7 @@ impl String { /// let s: String<12> = String::try_from("Hello").unwrap(); /// let view: &StringView = &s; /// ``` + #[inline] pub fn as_view(&self) -> &StringView { self } @@ -128,6 +129,7 @@ impl String { /// let mut s: String<12> = String::try_from("Hello").unwrap(); /// let view: &mut StringView = &mut s; /// ``` + #[inline] pub fn as_mut_view(&mut self) -> &mut StringView { self } @@ -352,6 +354,7 @@ impl String { /// assert_eq!(s, "olleh"); /// # Ok::<(), ()>(()) /// ``` + #[inline] pub unsafe fn as_mut_vec_view(&mut self) -> &mut VecView { self.as_mut_view().as_mut_vec_view() } From b847b156dc5507df5ceaddc08653ce5a79ed0f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Fri, 9 Feb 2024 09:51:27 +0100 Subject: [PATCH 10/11] Use macro for traits --- src/string.rs | 333 ++++++++++++++++++++------------------------------ 1 file changed, 134 insertions(+), 199 deletions(-) diff --git a/src/string.rs b/src/string.rs index cead240d1c..3c5e1d9c18 100644 --- a/src/string.rs +++ b/src/string.rs @@ -887,226 +887,161 @@ impl Clone for String { } } -impl fmt::Debug for String { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.as_view().fmt(f) - } -} - -impl fmt::Debug for StringView { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - ::fmt(self, f) - } -} - -impl fmt::Display for String { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.as_view().fmt(f) - } -} - -impl fmt::Display for StringView { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - ::fmt(self, f) - } -} - -impl hash::Hash for String { - #[inline] - fn hash(&self, hasher: &mut H) { - self.as_view().hash(hasher) - } -} - -impl hash::Hash for StringView { - #[inline] - fn hash(&self, hasher: &mut H) { - ::hash(self, hasher) - } -} - -impl fmt::Write for String { - fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { - self.as_mut_view().write_str(s) - } - - fn write_char(&mut self, c: char) -> Result<(), fmt::Error> { - self.as_mut_view().write_char(c) - } -} - -impl fmt::Write for StringView { - fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { - self.push_str(s).map_err(|_| fmt::Error) - } - - fn write_char(&mut self, c: char) -> Result<(), fmt::Error> { - self.push(c).map_err(|_| fmt::Error) - } -} - -impl ops::Deref for String { - type Target = str; - - fn deref(&self) -> &str { - self.as_view().deref() - } -} - -impl ops::Deref for StringView { - type Target = str; - - fn deref(&self) -> &str { - self.as_str() - } -} - -impl ops::DerefMut for String { - fn deref_mut(&mut self) -> &mut str { - self.as_mut_view().deref_mut() - } -} +macro_rules! imp_traits { + ($Ty:ident$()?) => { + // String/StringView == String + impl PartialEq> for $Ty<$($M)*> + { + #[inline] + fn eq(&self, other: &String) -> bool { + self.as_str().eq(other.as_str()) + } + } -impl ops::DerefMut for StringView { - fn deref_mut(&mut self) -> &mut str { - self.as_mut_str() - } -} + // String/StringView == StringView + impl<$(const $M: usize)*> PartialEq for $Ty<$($M)*> + { + #[inline] + fn eq(&self, other: &StringView) -> bool { + self.as_str().eq(other.as_str()) + } + } -impl AsRef for String { - #[inline] - fn as_ref(&self) -> &str { - self - } -} + // String/StringView == str + impl<$(const $M: usize)*> PartialEq for $Ty<$($M)*> + { + #[inline] + fn eq(&self, other: &str) -> bool { + self.as_str().eq(other) + } + } -impl AsRef for StringView { - #[inline] - fn as_ref(&self) -> &str { - self - } -} + // str == String/StringView + impl<$(const $M: usize)*> PartialEq<$Ty<$($M)*>> for str + { + #[inline] + fn eq(&self, other: &$Ty<$($M)*>) -> bool { + self.eq(other.as_str()) + } + } -impl AsRef<[u8]> for String { - #[inline] - fn as_ref(&self) -> &[u8] { - self.as_view().as_bytes() - } -} + // &str == String/StringView + impl<$(const $M: usize)*> PartialEq<&str> for $Ty<$($M)*> + { + #[inline] + fn eq(&self, other: &&str) -> bool { + (*self).as_str().eq(*other) + } + } -impl AsRef<[u8]> for StringView { - #[inline] - fn as_ref(&self) -> &[u8] { - self.as_bytes() - } -} + // String/StringView == str + impl<$(const $M: usize)*> PartialEq<$Ty<$($M)*>> for &str + { + #[inline] + fn eq(&self, other: &$Ty<$($M)*>) -> bool { + (*self).eq(other.as_str()) + } + } -impl PartialEq> for String { - fn eq(&self, rhs: &String) -> bool { - str::eq(&**self, &**rhs) - } -} + impl<$(const $M: usize)*> Eq for $Ty<$($M)*> {} -impl PartialEq for StringView { - fn eq(&self, rhs: &StringView) -> bool { - str::eq(&**self, &**rhs) - } -} + impl PartialOrd> for $Ty<$($M)*> + { + #[inline] + fn partial_cmp(&self, other: &String) -> Option { + Some(::cmp(self, other)) + } + } -// String == str -impl PartialEq for String { - #[inline] - fn eq(&self, other: &str) -> bool { - str::eq(self, other) - } -} + impl<$(const $M: usize)*> PartialOrd for $Ty<$($M)*> + { + #[inline] + fn partial_cmp(&self, other: &StringView) -> Option { + Some(::cmp(self, other)) + } + } -// StringView == str -impl PartialEq for StringView { - #[inline] - fn eq(&self, other: &str) -> bool { - str::eq(self, other) - } -} + impl<$(const $M: usize)*> Ord for $Ty<$($M)*> { + fn cmp(&self, other: &$Ty<$($M)*>) -> Ordering { + ::cmp(self, other) + } + } -// String == &'str -impl PartialEq<&str> for String { - #[inline] - fn eq(&self, other: &&str) -> bool { - str::eq(self, &other[..]) - } -} + impl<$(const $M: usize)*> fmt::Debug for $Ty<$($M)*> + { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + ::fmt(self, f) + } + } -// StringView == &'str -impl PartialEq<&str> for StringView { - #[inline] - fn eq(&self, other: &&str) -> bool { - str::eq(self, &other[..]) - } -} + impl<$(const $M: usize)*> fmt::Display for $Ty<$($M)*> + { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + ::fmt(self, f) + } + } -// str == String -impl PartialEq> for str { - #[inline] - fn eq(&self, other: &String) -> bool { - str::eq(self, &other[..]) - } -} + impl<$(const $M: usize)*> hash::Hash for $Ty<$($M)*> + { + #[inline] + fn hash(&self, hasher: &mut H) { + ::hash(self, hasher) + } + } -// str == StringView -impl PartialEq for str { - #[inline] - fn eq(&self, other: &StringView) -> bool { - str::eq(self, &other[..]) - } -} + impl<$(const $M: usize)*> fmt::Write for $Ty<$($M)*> + { + #[inline] + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + self.push_str(s).map_err(|_| fmt::Error) + } -// &'str == String -impl PartialEq> for &str { - #[inline] - fn eq(&self, other: &String) -> bool { - str::eq(self, &other[..]) - } -} + #[inline] + fn write_char(&mut self, c: char) -> Result<(), fmt::Error> { + self.push(c).map_err(|_| fmt::Error) + } + } -// &'str == StringView -impl PartialEq for &str { - #[inline] - fn eq(&self, other: &StringView) -> bool { - str::eq(self, &other[..]) - } -} + impl<$(const $M: usize)*> ops::Deref for $Ty<$($M)*> + { + type Target = str; -impl Eq for String {} -impl Eq for StringView {} + #[inline] + fn deref(&self) -> &str { + self.as_str() + } + } -impl PartialOrd> for String { - #[inline] - fn partial_cmp(&self, other: &String) -> Option { - Some(Ord::cmp(&**self, &**other)) - } -} + impl<$(const $M: usize)*> ops::DerefMut for $Ty<$($M)*> + { + #[inline] + fn deref_mut(&mut self) -> &mut str { + self.as_mut_str() + } + } -impl PartialOrd for StringView { - #[inline] - fn partial_cmp(&self, other: &StringView) -> Option { - Some(Ord::cmp(&**self, &**other)) - } -} + impl<$(const $M: usize)*> AsRef for $Ty<$($M)*> + { + #[inline] + fn as_ref(&self) -> &str { + self + } + } -impl Ord for String { - #[inline] - fn cmp(&self, other: &Self) -> Ordering { - Ord::cmp(&**self, &**other) - } + impl<$(const $M: usize)*> AsRef<[u8]> for $Ty<$($M)*> + { + #[inline] + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } + } + }; } -impl Ord for StringView { - #[inline] - fn cmp(&self, other: &Self) -> Ordering { - Ord::cmp(&**self, &**other) - } -} +imp_traits!(String); +imp_traits!(StringView); /// Equivalent to [`format`](https://doc.rust-lang.org/std/fmt/fn.format.html). /// From ce2d1c3fc3d85283e8ec9afed98b56758e361796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Thu, 23 May 2024 09:11:32 +0200 Subject: [PATCH 11/11] Implement reviews --- src/string.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/string.rs b/src/string.rs index 3c5e1d9c18..28c445d4ec 100644 --- a/src/string.rs +++ b/src/string.rs @@ -246,7 +246,7 @@ impl String { /// /// let s: String<4> = String::try_from("ab")?; /// let b = s.into_bytes(); - /// assert!(b.len() == 2); + /// assert_eq!(b.len(), 2); /// /// assert_eq!(&[b'a', b'b'], &b[..]); /// # Ok::<(), ()>(()) @@ -266,7 +266,7 @@ impl String { /// use heapless::String; /// /// let mut s: String<4> = String::try_from("ab")?; - /// assert!(s.as_str() == "ab"); + /// assert_eq!(s.as_str(), "ab"); /// /// let _s = s.as_str(); /// // s.push('c'); // <- cannot borrow `s` as mutable because it is also borrowed as immutable @@ -316,7 +316,7 @@ impl String { /// /// unsafe { /// let vec = s.as_mut_vec(); - /// assert_eq!(&b"hello", &vec); + /// assert_eq!(&vec, &b"hello"); /// /// vec.reverse(); /// } @@ -347,7 +347,7 @@ impl String { /// /// unsafe { /// let vec = s.as_mut_vec_view(); - /// assert_eq!(&b"hello", &vec); + /// assert_eq!(&vec, &b"hello"); /// /// vec.reverse(); /// } @@ -393,7 +393,7 @@ impl String { /// use heapless::String; /// /// let mut s: String<4> = String::new(); - /// assert!(s.capacity() == 4); + /// assert_eq!(s.capacity(), 4); /// ``` #[inline] pub fn capacity(&self) -> usize { @@ -415,7 +415,7 @@ impl String { /// s.push('2').unwrap(); /// s.push('3').unwrap(); /// - /// assert!("abc123" == s.as_str()); + /// assert_eq!("abc123", s.as_str()); /// /// assert_eq!("abc123", s); /// # Ok::<(), ()>(()) @@ -449,7 +449,7 @@ impl String { /// /// s.truncate(2); /// - /// assert_eq!("he", s); + /// assert_eq!(s, "he"); /// # Ok::<(), ()>(()) /// ``` #[inline] @@ -548,7 +548,7 @@ impl StringView { /// /// let mut s: String<4> = String::try_from("ab")?; /// let s: &mut StringView = &mut s; - /// assert!(s.as_str() == "ab"); + /// assert_eq!(s.as_str(), "ab"); /// /// let _s = s.as_str(); /// // s.push('c'); // <- cannot borrow `s` as mutable because it is also borrowed as immutable @@ -600,7 +600,7 @@ impl StringView { /// /// unsafe { /// let vec = s.as_mut_vec_view(); - /// assert_eq!(&b"hello", &vec); + /// assert_eq!(&vec, &b"hello"); /// /// vec.reverse(); /// } @@ -625,7 +625,7 @@ impl StringView { /// /// assert!(s.push_str("bar").is_ok()); /// - /// assert_eq!("foobar", s); + /// assert_eq!(s, "foobar"); /// /// assert!(s.push_str("tender").is_err()); /// # Ok::<(), ()>(()) @@ -647,7 +647,7 @@ impl StringView { /// /// let s: String<4> = String::new(); /// let s: &StringView = &s; - /// assert!(s.capacity() == 4); + /// assert_eq!(s.capacity(), 4); /// ``` #[inline] pub fn capacity(&self) -> usize { @@ -670,9 +670,9 @@ impl StringView { /// s.push('2').unwrap(); /// s.push('3').unwrap(); /// - /// assert!("abc123" == s.as_str()); + /// assert_eq!(s.as_str(), "abc123"); /// - /// assert_eq!("abc123", s); + /// assert_eq!(s, "abc123"); /// # Ok::<(), ()>(()) /// ``` #[inline] @@ -1199,7 +1199,7 @@ mod tests { #[test] fn empty() { let s: String<4> = String::new(); - assert!(s.capacity() == 4); + assert_eq!(s.capacity(), 4); assert_eq!(s, ""); assert_eq!(s.len(), 0); assert_ne!(s.len(), 4); @@ -1208,7 +1208,7 @@ mod tests { #[test] fn try_from() { let s: String<4> = String::try_from("123").unwrap(); - assert!(s.len() == 3); + assert_eq!(s.len(), 3); assert_eq!(s, "123"); let _: () = String::<2>::try_from("123").unwrap_err(); @@ -1219,7 +1219,7 @@ mod tests { use core::str::FromStr; let s: String<4> = String::<4>::from_str("123").unwrap(); - assert!(s.len() == 3); + assert_eq!(s.len(), 3); assert_eq!(s, "123"); let _: () = String::<2>::from_str("123").unwrap_err(); @@ -1297,7 +1297,7 @@ mod tests { assert!(s.push('2').is_ok()); assert!(s.push('3').is_ok()); assert!(s.push('4').is_err()); - assert!("abc123" == s.as_str()); + assert_eq!("abc123", s.as_str()); } #[test]