diff --git a/CHANGELOG.md b/CHANGELOG.md index b63bd39b51..9992d86028 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added `String::drain`. - Implemented `DoubleEndedIterator` for `OldestOrdered`. - Added std `Entry` methods to indexmap `Entry`. +- Added `StringView`, the `!Sized` version of `String`. ### Changed diff --git a/src/defmt.rs b/src/defmt.rs index c50963c007..6d547c09b7 100644 --- a/src/defmt.rs +++ b/src/defmt.rs @@ -1,6 +1,6 @@ //! Defmt implementations for heapless types -use crate::{storage::Storage, vec::VecInner}; +use crate::{storage::Storage, string::StringInner, vec::VecInner}; use defmt::Formatter; impl defmt::Format for VecInner @@ -12,7 +12,7 @@ where } } -impl defmt::Format for crate::String +impl defmt::Format for StringInner where u8: defmt::Format, { diff --git a/src/ser.rs b/src/ser.rs index 9839da2ac4..cdd4b6bf4b 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, storage::Storage, vec::VecInner, BinaryHeap, Deque, - HistoryBuffer, IndexMap, IndexSet, LinearMap, String, + binary_heap::Kind as BinaryHeapKind, storage::Storage, string::StringInner, vec::VecInner, + BinaryHeap, Deque, HistoryBuffer, IndexMap, IndexSet, LinearMap, }; use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}; @@ -129,10 +129,10 @@ where // String containers -impl Serialize for String { - fn serialize(&self, serializer: S) -> Result +impl Serialize for StringInner { + fn serialize(&self, serializer: SER) -> Result where - S: Serializer, + SER: Serializer, { serializer.serialize_str(&*self) } diff --git a/src/string/drain.rs b/src/string/drain.rs index c547e5f4b2..11a708c2e7 100644 --- a/src/string/drain.rs +++ b/src/string/drain.rs @@ -1,17 +1,17 @@ use core::{fmt, iter::FusedIterator, str::Chars}; -use super::String; +use super::StringView; /// A draining iterator for `String`. /// -/// This struct is created by the [`drain`] method on [`String`]. See its +/// This struct is created by the [`drain`] method on [`crate::String`]. See its /// documentation for more. /// -/// [`drain`]: String::drain -pub struct Drain<'a, const N: usize> { +/// [`drain`]: crate::String::drain +pub struct Drain<'a> { /// Will be used as &'a mut String in the destructor - pub(super) string: *mut String, - /// Start of part to remove + pub(super) string: *mut StringView, + /// Stast of part to remove pub(super) start: usize, /// End of part to remove pub(super) end: usize, @@ -19,16 +19,16 @@ pub struct Drain<'a, const N: usize> { pub(super) iter: Chars<'a>, } -impl fmt::Debug for Drain<'_, N> { +impl fmt::Debug for Drain<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("Drain").field(&self.as_str()).finish() } } -unsafe impl Sync for Drain<'_, N> {} -unsafe impl Send for Drain<'_, N> {} +unsafe impl Sync for Drain<'_> {} +unsafe impl Send for Drain<'_> {} -impl Drop for Drain<'_, N> { +impl Drop for Drain<'_> { fn drop(&mut self) { unsafe { // Use `Vec::drain`. “Reaffirm” the bounds checks to avoid @@ -41,7 +41,7 @@ impl Drop for Drain<'_, N> { } } -impl<'a, const N: usize> Drain<'a, N> { +impl<'a> Drain<'a> { /// Returns the remaining (sub)string of this iterator as a slice. /// /// # Examples @@ -61,19 +61,19 @@ impl<'a, const N: usize> Drain<'a, N> { } } -impl AsRef for Drain<'_, N> { +impl AsRef for Drain<'_> { fn as_ref(&self) -> &str { self.as_str() } } -impl AsRef<[u8]> for Drain<'_, N> { +impl AsRef<[u8]> for Drain<'_> { fn as_ref(&self) -> &[u8] { self.as_str().as_bytes() } } -impl Iterator for Drain<'_, N> { +impl Iterator for Drain<'_> { type Item = char; #[inline] @@ -91,18 +91,18 @@ impl Iterator for Drain<'_, N> { } } -impl DoubleEndedIterator for Drain<'_, N> { +impl DoubleEndedIterator for Drain<'_> { #[inline] fn next_back(&mut self) -> Option { self.iter.next_back() } } -impl FusedIterator for Drain<'_, N> {} +impl FusedIterator for Drain<'_> {} #[cfg(test)] mod tests { - use super::String; + use crate::String; #[test] fn drain_front() { diff --git a/src/string/mod.rs b/src/string/mod.rs index ceeac8455e..cabc02e01f 100644 --- a/src/string/mod.rs +++ b/src/string/mod.rs @@ -10,7 +10,11 @@ use core::{ str::{self, Utf8Error}, }; -use crate::Vec; +use crate::{ + storage::{OwnedStorage, Storage, ViewStorage}, + vec::VecInner, + Vec, +}; mod drain; pub use drain::Drain; @@ -37,9 +41,83 @@ impl fmt::Display for FromUtf16Error { } } +/// Base struct for [`String`] and [`StringView`], generic over the [`Storage`]. +/// +/// In most cases you should use [`String`] or [`StringView`] directly. Only use this +/// struct if you want to write code that's generic over both. +pub struct StringInner { + vec: VecInner, +} + /// A fixed capacity [`String`](https://doc.rust-lang.org/std/string/struct.String.html). -pub struct String { - vec: Vec, +pub type String = StringInner>; + +/// A dynamic capacity [`String`](https://doc.rust-lang.org/std/string/struct.String.html). +pub type StringView = StringInner; + +impl StringView { + /// Removes the specified range from the string in bulk, returning all + /// removed characters as an iterator. + /// + /// The returned iterator keeps a mutable borrow on the string to optimize + /// its implementation. + /// + /// # Panics + /// + /// Panics if the starting point or end point do not lie on a [`char`] + /// boundary, or if they're out of bounds. + /// + /// # Leaking + /// + /// If the returned iterator goes out of scope without being dropped (due to + /// [`core::mem::forget`], for example), the string may still contain a copy + /// of any drained characters, or may have lost characters arbitrarily, + /// including characters outside the range. + /// + /// # Examples + /// + /// ``` + /// use heapless::String; + /// + /// let mut s = String::<32>::try_from("α is alpha, β is beta").unwrap(); + /// let beta_offset = s.find('β').unwrap_or(s.len()); + /// + /// // Remove the range up until the β from the string + /// let t: String<32> = s.drain(..beta_offset).collect(); + /// assert_eq!(t, "α is alpha, "); + /// assert_eq!(s, "β is beta"); + /// + /// // A full range clears the string, like `clear()` does + /// s.drain(..); + /// assert_eq!(s, ""); + /// ``` + pub fn drain(&mut self, range: R) -> Drain<'_> + where + R: RangeBounds, + { + // Memory safety + // + // The `String` version of `Drain` does not have the memory safety issues + // of the `Vec` version. The data is just plain bytes. + // Because the range removal happens in `Drop`, if the `Drain` iterator is leaked, + // the removal will not happen. + let Range { start, end } = crate::slice::range(range, ..self.len()); + assert!(self.is_char_boundary(start)); + assert!(self.is_char_boundary(end)); + + // Take out two simultaneous borrows. The &mut String won't be accessed + // until iteration is over, in Drop. + let self_ptr = self as *mut _; + // SAFETY: `slice::range` and `is_char_boundary` do the appropriate bounds checks. + let chars_iter = unsafe { self.get_unchecked(start..end) }.chars(); + + Drain { + start, + end, + iter: chars_iter, + string: self_ptr, + } + } } impl String { @@ -185,6 +263,92 @@ impl String { self.vec } + /// Get a reference to the `String`, erasing the `N` const-generic. + /// + /// + /// ```rust + /// # use heapless::string::{String, StringView}; + /// let s: String<10> = 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<10> = String::try_from("hello").unwrap(); + /// let view: &StringView = &s; + /// ``` + #[inline] + 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<10> = 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<10> = String::try_from("hello").unwrap(); + /// let view: &mut StringView = &mut s; + /// ``` + #[inline] + pub fn as_mut_view(&mut self) -> &mut StringView { + self + } + + /// Removes the specified range from the string in bulk, returning all + /// removed characters as an iterator. + /// + /// The returned iterator keeps a mutable borrow on the string to optimize + /// its implementation. + /// + /// # Panics + /// + /// Panics if the starting point or end point do not lie on a [`char`] + /// boundary, or if they're out of bounds. + /// + /// # Leaking + /// + /// If the returned iterator goes out of scope without being dropped (due to + /// [`core::mem::forget`], for example), the string may still contain a copy + /// of any drained characters, or may have lost characters arbitrarily, + /// including characters outside the range. + /// + /// # Examples + /// + /// ``` + /// use heapless::String; + /// + /// let mut s = String::<32>::try_from("α is alpha, β is beta").unwrap(); + /// let beta_offset = s.find('β').unwrap_or(s.len()); + /// + /// // Remove the range up until the β from the string + /// let t: String<32> = s.drain(..beta_offset).collect(); + /// assert_eq!(t, "α is alpha, "); + /// assert_eq!(s, "β is beta"); + /// + /// // A full range clears the string, like `clear()` does + /// s.drain(..); + /// assert_eq!(s, ""); + /// ``` + pub fn drain(&mut self, range: R) -> Drain<'_> + where + R: RangeBounds, + { + self.as_mut_view().drain(range) + } +} + +impl StringInner { /// Extracts a string slice containing the entire string. /// /// # Examples @@ -252,7 +416,7 @@ impl String { /// assert_eq!(s, "olleh"); /// # Ok::<(), ()>(()) /// ``` - pub unsafe fn as_mut_vec(&mut self) -> &mut Vec { + pub unsafe fn as_mut_vec(&mut self) -> &mut VecInner { &mut self.vec } @@ -460,69 +624,6 @@ impl String { pub fn clear(&mut self) { self.vec.clear() } - - /// Removes the specified range from the string in bulk, returning all - /// removed characters as an iterator. - /// - /// The returned iterator keeps a mutable borrow on the string to optimize - /// its implementation. - /// - /// # Panics - /// - /// Panics if the starting point or end point do not lie on a [`char`] - /// boundary, or if they're out of bounds. - /// - /// # Leaking - /// - /// If the returned iterator goes out of scope without being dropped (due to - /// [`core::mem::forget`], for example), the string may still contain a copy - /// of any drained characters, or may have lost characters arbitrarily, - /// including characters outside the range. - /// - /// # Examples - /// - /// ``` - /// use heapless::String; - /// - /// let mut s = String::<32>::try_from("α is alpha, β is beta").unwrap(); - /// let beta_offset = s.find('β').unwrap_or(s.len()); - /// - /// // Remove the range up until the β from the string - /// let t: String<32> = s.drain(..beta_offset).collect(); - /// assert_eq!(t, "α is alpha, "); - /// assert_eq!(s, "β is beta"); - /// - /// // A full range clears the string, like `clear()` does - /// s.drain(..); - /// assert_eq!(s, ""); - /// ``` - pub fn drain(&mut self, range: R) -> Drain<'_, N> - where - R: RangeBounds, - { - // Memory safety - // - // The `String` version of `Drain` does not have the memory safety issues - // of the `Vec` version. The data is just plain bytes. - // Because the range removal happens in `Drop`, if the `Drain` iterator is leaked, - // the removal will not happen. - let Range { start, end } = crate::slice::range(range, ..self.len()); - assert!(self.is_char_boundary(start)); - assert!(self.is_char_boundary(end)); - - // Take out two simultaneous borrows. The &mut String won't be accessed - // until iteration is over, in Drop. - let self_ptr = self as *mut _; - // SAFETY: `slice::range` and `is_char_boundary` do the appropriate bounds checks. - let chars_iter = unsafe { self.get_unchecked(start..end) }.chars(); - - Drain { - start, - end, - iter: chars_iter, - string: self_ptr, - } - } } impl Default for String { @@ -588,26 +689,26 @@ impl Clone for String { } } -impl fmt::Debug for String { +impl fmt::Debug for StringInner { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ::fmt(self, f) } } -impl fmt::Display for String { +impl fmt::Display for StringInner { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ::fmt(self, f) } } -impl hash::Hash for String { +impl hash::Hash for StringInner { #[inline] fn hash(&self, hasher: &mut H) { ::hash(self, hasher) } } -impl fmt::Write for String { +impl fmt::Write for StringInner { fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { self.push_str(s).map_err(|_| fmt::Error) } @@ -617,7 +718,7 @@ impl fmt::Write for String { } } -impl ops::Deref for String { +impl ops::Deref for StringInner { type Target = str; fn deref(&self) -> &str { @@ -625,34 +726,34 @@ impl ops::Deref for String { } } -impl ops::DerefMut for String { +impl ops::DerefMut for StringInner { fn deref_mut(&mut self) -> &mut str { self.as_mut_str() } } -impl AsRef for String { +impl AsRef for StringInner { #[inline] fn as_ref(&self) -> &str { self } } -impl AsRef<[u8]> for String { +impl AsRef<[u8]> for StringInner { #[inline] fn as_ref(&self) -> &[u8] { self.as_bytes() } } -impl PartialEq> for String { - fn eq(&self, rhs: &String) -> bool { +impl PartialEq> for StringInner { + fn eq(&self, rhs: &StringInner) -> bool { str::eq(&**self, &**rhs) } } // String == str -impl PartialEq for String { +impl PartialEq for StringInner { #[inline] fn eq(&self, other: &str) -> bool { str::eq(self, other) @@ -660,7 +761,7 @@ impl PartialEq for String { } // String == &'str -impl PartialEq<&str> for String { +impl PartialEq<&str> for StringInner { #[inline] fn eq(&self, other: &&str) -> bool { str::eq(self, &other[..]) @@ -668,31 +769,31 @@ impl PartialEq<&str> for String { } // str == String -impl PartialEq> for str { +impl PartialEq> for str { #[inline] - fn eq(&self, other: &String) -> bool { + fn eq(&self, other: &StringInner) -> bool { str::eq(self, &other[..]) } } // &'str == String -impl PartialEq> for &str { +impl PartialEq> for &str { #[inline] - fn eq(&self, other: &String) -> bool { + fn eq(&self, other: &StringInner) -> bool { str::eq(self, &other[..]) } } -impl Eq for String {} +impl Eq for StringInner {} -impl PartialOrd> for String { +impl PartialOrd> for StringInner { #[inline] - fn partial_cmp(&self, other: &String) -> Option { + fn partial_cmp(&self, other: &StringInner) -> Option { PartialOrd::partial_cmp(&**self, &**other) } } -impl Ord for String { +impl Ord for StringInner { #[inline] fn cmp(&self, other: &Self) -> Ordering { Ord::cmp(&**self, &**other) diff --git a/src/ufmt.rs b/src/ufmt.rs index fd0b276c40..002ce04c46 100644 --- a/src/ufmt.rs +++ b/src/ufmt.rs @@ -1,7 +1,7 @@ -use crate::{storage::Storage, string::String, vec::VecInner}; +use crate::{storage::Storage, string::StringInner, vec::VecInner}; use ufmt_write::uWrite; -impl uWrite for String { +impl uWrite for StringInner { type Error = (); fn write_str(&mut self, s: &str) -> Result<(), Self::Error> { self.push_str(s)