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 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 diff --git a/src/string.rs b/src/string.rs index dce6394d2d..28c445d4ec 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. /// @@ -59,6 +94,46 @@ 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; + /// ``` + #[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<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; + /// ``` + #[inline] + 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. /// @@ -171,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::<(), ()>(()) @@ -191,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 @@ -199,7 +274,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. @@ -218,7 +293,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`. @@ -241,7 +316,7 @@ impl String { /// /// unsafe { /// let vec = s.as_mut_vec(); - /// assert_eq!(&[104, 101, 108, 108, 111][..], &vec[..]); + /// assert_eq!(&vec, &b"hello"); /// /// vec.reverse(); /// } @@ -252,6 +327,38 @@ 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!(&vec, &b"hello"); + /// + /// vec.reverse(); + /// } + /// 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() + } + /// Appends a given string slice onto the end of this `String`. /// /// # Examples @@ -273,7 +380,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. @@ -286,11 +393,11 @@ 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 { - self.vec.capacity() + self.as_view().capacity() } /// Appends the given [`char`] to the end of this `String`. @@ -308,13 +415,268 @@ 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::<(), ()>(()) /// ``` #[inline] #[allow(clippy::result_unit_err)] + pub fn push(&mut self, c: char) -> Result<(), ()> { + self.as_mut_view().push(c) + } + + /// 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!(s, "he"); + /// # Ok::<(), ()>(()) + /// ``` + #[inline] + pub fn truncate(&mut self, new_len: usize) { + self.as_mut_view().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 { + self.as_mut_view().pop() + } + + /// 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 { + self.as_mut_view().remove(index) + } + + /// 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.as_mut_view().clear() + } +} + +impl StringView { + /// Extracts a string slice containing the entire string. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use heapless::string::{String, StringView}; + /// + /// let mut s: String<4> = String::try_from("ab")?; + /// let s: &mut StringView = &mut s; + /// 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 + /// # 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::{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::<(), ()>(()) + /// ``` + #[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::{String, StringView}; + /// + /// let mut s: String<8> = String::try_from("hello")?; + /// let s: &mut StringView = &mut s; + /// + /// unsafe { + /// let vec = s.as_mut_vec_view(); + /// assert_eq!(&vec, &b"hello"); + /// + /// 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::{String, StringView}; + /// + /// let mut s: String<8> = String::try_from("foo")?; + /// let s: &mut StringView = &mut s; + /// + /// assert!(s.push_str("bar").is_ok()); + /// + /// assert_eq!(s, "foobar"); + /// + /// 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::{String, StringView}; + /// + /// let s: String<4> = String::new(); + /// let s: &StringView = &s; + /// assert_eq!(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::{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(); + /// s.push('3').unwrap(); + /// + /// assert_eq!(s.as_str(), "abc123"); + /// + /// assert_eq!(s, "abc123"); + /// # 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(|_| {}), @@ -341,9 +703,10 @@ impl String { /// 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); /// @@ -367,9 +730,10 @@ impl String { /// 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')); @@ -406,9 +770,10 @@ impl String { /// 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'); @@ -441,9 +806,10 @@ impl String { /// 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(); /// @@ -521,117 +887,162 @@ impl Clone for String { } } -impl fmt::Debug for String { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - ::fmt(self, f) - } -} +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 fmt::Display for String { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - ::fmt(self, f) - } -} + // 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 hash::Hash for String { - #[inline] - fn hash(&self, hasher: &mut H) { - ::hash(self, hasher) - } -} + // String/StringView == str + impl<$(const $M: usize)*> PartialEq for $Ty<$($M)*> + { + #[inline] + fn eq(&self, other: &str) -> bool { + self.as_str().eq(other) + } + } -impl fmt::Write for String { - fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { - self.push_str(s).map_err(|_| fmt::Error) - } + // 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()) + } + } - fn write_char(&mut self, c: char) -> Result<(), fmt::Error> { - self.push(c).map_err(|_| fmt::Error) - } -} + // &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 ops::Deref for String { - type Target = str; + // 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()) + } + } - fn deref(&self) -> &str { - self.as_str() - } -} + impl<$(const $M: usize)*> Eq for $Ty<$($M)*> {} -impl ops::DerefMut for String { - fn deref_mut(&mut self) -> &mut str { - self.as_mut_str() - } -} + impl PartialOrd> for $Ty<$($M)*> + { + #[inline] + fn partial_cmp(&self, other: &String) -> Option { + Some(::cmp(self, other)) + } + } -impl AsRef for String { - #[inline] - fn as_ref(&self) -> &str { - self - } -} + impl<$(const $M: usize)*> PartialOrd for $Ty<$($M)*> + { + #[inline] + fn partial_cmp(&self, other: &StringView) -> Option { + Some(::cmp(self, other)) + } + } -impl AsRef<[u8]> for String { - #[inline] - fn as_ref(&self) -> &[u8] { - self.as_bytes() - } -} + impl<$(const $M: usize)*> Ord for $Ty<$($M)*> { + fn cmp(&self, other: &$Ty<$($M)*>) -> Ordering { + ::cmp(self, other) + } + } -impl PartialEq> for String { - fn eq(&self, rhs: &String) -> bool { - str::eq(&**self, &**rhs) - } -} + impl<$(const $M: usize)*> fmt::Debug for $Ty<$($M)*> + { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + ::fmt(self, f) + } + } -// String == str -impl PartialEq for String { - #[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) + } + } -// String == &'str -impl PartialEq<&str> for String { - #[inline] - fn eq(&self, other: &&str) -> 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 == String -impl PartialEq> for str { - #[inline] - fn eq(&self, other: &String) -> 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) + } + } -impl Eq for String {} + impl<$(const $M: usize)*> ops::Deref for $Ty<$($M)*> + { + type Target = str; -impl PartialOrd> for String { - #[inline] - fn partial_cmp(&self, other: &String) -> Option { - PartialOrd::partial_cmp(&**self, &**other) - } -} + #[inline] + fn deref(&self) -> &str { + self.as_str() + } + } -impl Ord for String { - #[inline] - fn cmp(&self, other: &Self) -> Ordering { - 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<$(const $M: usize)*> AsRef for $Ty<$($M)*> + { + #[inline] + fn as_ref(&self) -> &str { + self + } + } + + impl<$(const $M: usize)*> AsRef<[u8]> for $Ty<$($M)*> + { + #[inline] + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } + } + }; } +imp_traits!(String); +imp_traits!(StringView); + /// Equivalent to [`format`](https://doc.rust-lang.org/std/fmt/fn.format.html). /// /// Please note that using [`format!`] might be preferable. @@ -788,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); @@ -797,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(); @@ -808,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(); @@ -886,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]