diff --git a/Cargo.toml b/Cargo.toml index da2f295..5c9c618 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,11 +27,11 @@ criterion = { version = "0.4", features = ["html_reports"] } env_logger = "0.10" serde = { version = "1.0", features = ["derive"] } diesel = { version = "2", features = ["sqlite", "postgres", "mysql"] } - [dependencies] log = { version = "0.4", optional = true } serde = { version = "1", optional = true } diesel = { version = "2", optional = true } +no-panic = "0.1" [features] default = ["std"] @@ -43,3 +43,6 @@ diesel-traits = ["diesel"] [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docs_rs_workaraound"] features = ["logs", "serde-traits", "std", "diesel-traits"] + +[profile.dev] +opt-level = 3 \ No newline at end of file diff --git a/src/arraystring.rs b/src/arraystring.rs index a0ae5a8..9f235ba 100644 --- a/src/arraystring.rs +++ b/src/arraystring.rs @@ -1,13 +1,16 @@ //! `ArrayString` definition and Api implementation -use crate::utils::{encode_char_utf8_unchecked, is_char_boundary, is_inside_boundary, never}; -use crate::utils::{shift_left_unchecked, shift_right_unchecked, truncate_str, IntoLossy}; +use crate::arraystring::sealed::ValidCapacity; +use crate::utils::{is_char_boundary, is_inside_boundary}; +use crate::utils::{truncate_str, IntoLossy}; use crate::{prelude::*, Error}; use core::char::{decode_utf16, REPLACEMENT_CHARACTER}; -use core::str::{from_utf8, from_utf8_unchecked}; -use core::{cmp::min, ops::*, ptr::copy_nonoverlapping}; +use core::str::from_utf8; +use core::{cmp::min, ops::*}; #[cfg(feature = "logs")] use log::{debug, trace}; +use no_panic::no_panic; +use std::{ptr, usize}; /// String based on a generic array (size defined at compile time through `const generics`) /// @@ -31,20 +34,20 @@ pub struct ArrayString { impl ArrayString where - Self: sealed::ValidCapacity, + Self: ValidCapacity, { /// Creates new empty string. /// /// ```rust /// # use arraystring::prelude::*; - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let string = ArrayString::<23>::new(); /// assert!(string.is_empty()); /// ``` #[inline] pub const fn new() -> Self { Self { - array: [0; N], + array: [0; N], // unsafe { MaybeUninit::uninit().assume_init() }, < miri says UB even if not really, lots faster size: 0, } } @@ -55,7 +58,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let string = ArrayString::<23>::try_from_str("My String")?; /// assert_eq!(string.as_str(), "My String"); /// @@ -80,7 +83,7 @@ where /// /// ```rust /// # use arraystring::prelude::*; - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let string = ArrayString::<23>::from_str_truncate("My String"); /// # assert_eq!(string.as_str(), "My String"); /// println!("{}", string); @@ -98,34 +101,6 @@ where s } - /// Creates new `ArrayString` from string slice assuming length is appropriate. - /// - /// # Safety - /// - /// It's UB if `string.len()` > [`capacity`]. - /// - /// [`capacity`]: ./struct.ArrayString.html#method.capacity - /// - /// ```rust - /// # use arraystring::prelude::*; - /// let filled = "0".repeat(ArrayString::<23>::capacity().into()); - /// let string = unsafe { - /// ArrayString::<23>::from_str_unchecked(&filled) - /// }; - /// assert_eq!(string.as_str(), filled.as_str()); - /// - /// // Undefined behavior, don't do it - /// // let out_of_bounds = "0".repeat(ArrayString::<23>::capacity().into() + 1); - /// // let ub = unsafe { ArrayString::<23>::from_str_unchecked(out_of_bounds) }; - /// ``` - #[inline] - pub unsafe fn from_str_unchecked(string: impl AsRef) -> Self { - trace!("FromStr unchecked: {}", string.as_ref()); - let mut s = Self::new(); - s.push_str_unchecked(string); - s - } - /// Creates new `ArrayString` from string slice iterator if total length is lower or equal to [`capacity`], otherwise returns an error. /// /// [`capacity`]: ./struct.ArrayString.html#method.capacity @@ -160,7 +135,7 @@ where /// ```rust /// # use arraystring::prelude::*; /// # fn main() -> Result<(), OutOfBounds> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let string = ArrayString::<255>::from_iterator_truncate(&["My String", " Other String"][..]); /// assert_eq!(string.as_str(), "My String Other String"); /// @@ -185,37 +160,6 @@ where out } - /// Creates new `ArrayString` from string slice iterator assuming length is appropriate. - /// - /// # Safety - /// - /// It's UB if `iter.map(|c| c.len()).sum()` > [`capacity`]. - /// - /// [`capacity`]: ./struct.ArrayString.html#method.capacity - /// - /// ```rust - /// # use arraystring::prelude::*; - /// let string = unsafe { - /// ArrayString::<255>::from_iterator_unchecked(&["My String", " My Other String"][..]) - /// }; - /// assert_eq!(string.as_str(), "My String My Other String"); - /// - /// // Undefined behavior, don't do it - /// // let out_of_bounds = (0..400).map(|_| "000"); - /// // let undefined_behavior = unsafe { - /// // ArrayString::<23>::from_iterator_unchecked(out_of_bounds) - /// // }; - /// ``` - #[inline] - pub unsafe fn from_iterator_unchecked(iter: impl IntoIterator>) -> Self { - trace!("FromIterator unchecked"); - let mut out = Self::new(); - for s in iter { - out.push_str_unchecked(s); - } - out - } - /// Creates new `ArrayString` from char iterator if total length is lower or equal to [`capacity`], otherwise returns an error. /// /// [`capacity`]: ./struct.ArrayString.html#method.capacity @@ -223,7 +167,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let string = ArrayString::<23>::try_from_chars("My String".chars())?; /// assert_eq!(string.as_str(), "My String"); /// @@ -248,7 +192,7 @@ where /// /// ```rust /// # use arraystring::prelude::*; - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let string = ArrayString::<23>::from_chars_truncate("My String".chars()); /// assert_eq!(string.as_str(), "My String"); /// @@ -270,33 +214,6 @@ where out } - /// Creates new `ArrayString` from char iterator assuming length is appropriate. - /// - /// # Safety - /// - /// It's UB if `iter.map(|c| c.len_utf8()).sum()` > [`capacity`]. - /// - /// [`capacity`]: ./struct.ArrayString.html#method.capacity - /// - /// ```rust - /// # use arraystring::prelude::*; - /// let string = unsafe { ArrayString::<23>::from_chars_unchecked("My String".chars()) }; - /// assert_eq!(string.as_str(), "My String"); - /// - /// // Undefined behavior, don't do it - /// // let out_of_bounds = "000".repeat(400); - /// // let undefined_behavior = unsafe { ArrayString::<23>::from_chars_unchecked(out_of_bounds.chars()) }; - /// ``` - #[inline] - pub unsafe fn from_chars_unchecked(iter: impl IntoIterator) -> Self { - trace!("From chars unchecked"); - let mut out = Self::new(); - for c in iter { - out.push_unchecked(c) - } - out - } - /// Creates new `ArrayString` from byte slice, returning [`Utf8`] on invalid utf-8 data or [`OutOfBounds`] if bigger than [`capacity`] /// /// [`Utf8`]: ./error/enum.Error.html#variant.Utf8 @@ -306,7 +223,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let string = ArrayString::<23>::try_from_utf8("My String")?; /// assert_eq!(string.as_str(), "My String"); /// @@ -332,7 +249,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let string = ArrayString::<23>::from_utf8_truncate("My String")?; /// assert_eq!(string.as_str(), "My String"); /// @@ -351,30 +268,6 @@ where Ok(Self::from_str_truncate(from_utf8(slice.as_ref())?)) } - /// Creates new `ArrayString` from byte slice assuming it's utf-8 and of a appropriate size. - /// - /// # Safety - /// - /// It's UB if `slice` is not a valid utf-8 string or `slice.len()` > [`capacity`]. - /// - /// [`capacity`]: ./struct.ArrayString.html#method.capacity - /// - /// ```rust - /// # use arraystring::prelude::*; - /// let string = unsafe { ArrayString::<23>::from_utf8_unchecked("My String") }; - /// assert_eq!(string.as_str(), "My String"); - /// - /// // Undefined behavior, don't do it - /// // let out_of_bounds = "0".repeat(300); - /// // let ub = unsafe { ArrayString::<23>::from_utf8_unchecked(out_of_bounds)) }; - /// ``` - #[inline] - pub unsafe fn from_utf8_unchecked(slice: impl AsRef<[u8]>) -> Self { - trace!("From utf8 unchecked: {:?}", slice.as_ref()); - debug_assert!(from_utf8(slice.as_ref()).is_ok()); - Self::from_str_unchecked(from_utf8_unchecked(slice.as_ref())) - } - /// Creates new `ArrayString` from `u16` slice, returning [`Utf16`] on invalid utf-16 data or [`OutOfBounds`] if bigger than [`capacity`] /// /// [`Utf16`]: ./error/enum.Error.html#variant.Utf16 @@ -384,7 +277,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let music = [0xD834, 0xDD1E, 0x006d, 0x0075, 0x0073, 0x0069, 0x0063]; /// let string = ArrayString::<23>::try_from_utf16(music)?; /// assert_eq!(string.as_str(), "𝄞music"); @@ -415,7 +308,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let music = [0xD834, 0xDD1E, 0x006d, 0x0075, 0x0073, 0x0069, 0x0063]; /// let string = ArrayString::<23>::from_utf16_truncate(music)?; /// assert_eq!(string.as_str(), "𝄞music"); @@ -448,7 +341,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let music = [0xD834, 0xDD1E, 0x006d, 0x0075, 0x0073, 0x0069, 0x0063]; /// let string = ArrayString::<23>::from_utf16_lossy_truncate(music); /// assert_eq!(string.as_str(), "𝄞music"); @@ -479,7 +372,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let s = ArrayString::<23>::try_from_str("My String")?; /// assert_eq!(s.as_str(), "My String"); /// # Ok(()) @@ -496,7 +389,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = ArrayString::<23>::try_from_str("My String")?; /// assert_eq!(s.as_mut_str(), "My String"); /// # Ok(()) @@ -513,7 +406,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let s = ArrayString::<23>::try_from_str("My String")?; /// assert_eq!(s.as_bytes(), "My String".as_bytes()); /// # Ok(()) @@ -550,12 +443,12 @@ where /// /// ```rust /// # use arraystring::prelude::*; - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// assert_eq!(ArrayString::<32>::capacity(), 32); /// ``` #[inline] - pub const fn capacity() -> u8 { - N as u8 + pub const fn capacity() -> usize { + N } /// Pushes string slice to the end of the `ArrayString` if total size is lower or equal to [`capacity`], otherwise returns an error. @@ -565,7 +458,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = ArrayString::<255>::try_from_str("My String")?; /// s.try_push_str(" My other String")?; /// assert_eq!(s.as_str(), "My String My other String"); @@ -575,12 +468,23 @@ where /// # } /// ``` #[inline] + #[cfg_attr(not(debug_assertions), no_panic)] pub fn try_push_str(&mut self, string: impl AsRef) -> Result<(), OutOfBounds> { trace!("Push str: {}", string.as_ref()); - let new_end = string.as_ref().len().saturating_add(self.len().into()); - is_inside_boundary(new_end, Self::capacity())?; - unsafe { self.push_str_unchecked(string) }; - Ok(()) + let str = string.as_ref().as_bytes(); + if str.len() == 0 { + return Ok(()); + } + is_inside_boundary(str.len() + self.len(), Self::capacity())?; + unsafe { + ptr::copy_nonoverlapping( + str.as_ptr(), + self.array.as_mut_ptr().add(self.len()), + str.len(), + ); + } + self.size += str.len() as u8; + return Ok(()); } /// Pushes string slice to the end of the `ArrayString` truncating total size if bigger than [`capacity`]. @@ -590,7 +494,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = ArrayString::<255>::try_from_str("My String")?; /// s.push_str_truncate(" My other String"); /// assert_eq!(s.as_str(), "My String My other String"); @@ -602,42 +506,23 @@ where /// # } /// ``` #[inline] + #[cfg_attr(not(debug_assertions), no_panic)] pub fn push_str_truncate(&mut self, string: impl AsRef) { trace!("Push str truncate: {}", string.as_ref()); - let size = Self::capacity().saturating_sub(self.len()); - unsafe { self.push_str_unchecked(truncate_str(string.as_ref(), size.into())) } - } - - /// Pushes string slice to the end of the `ArrayString` assuming total size is appropriate. - /// - /// # Safety - /// - /// It's UB if `self.len() + string.len()` > [`capacity`]. - /// - /// [`capacity`]: ./struct.ArrayString.html#method.capacity - /// - /// ```rust - /// # use arraystring::{Error, prelude::*}; - /// # fn main() -> Result<(), Error> { - /// let mut s = ArrayString::<255>::try_from_str("My String")?; - /// unsafe { s.push_str_unchecked(" My other String") }; - /// assert_eq!(s.as_str(), "My String My other String"); - /// - /// // Undefined behavior, don't do it - /// // let mut undefined_behavior = ArrayString::<23>::default(); - /// // undefined_behavior.push_str_unchecked("0".repeat(ArrayString::<23>::capacity().into() + 1)); - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub unsafe fn push_str_unchecked(&mut self, string: impl AsRef) { - let (s, len) = (string.as_ref(), string.as_ref().len()); - debug!("Push str unchecked: {} ({} + {})", s, self.len(), len); - debug_assert!(len.saturating_add(self.len().into()) <= Self::capacity() as usize); - - let dest = self.as_mut_bytes().as_mut_ptr().add(self.len().into()); - copy_nonoverlapping(s.as_ptr(), dest, len); - self.size = self.size.saturating_add(len.into_lossy()); + let str = string.as_ref().as_bytes(); + let size = Self::capacity() - self.len(); + if size == 0 || str.len() == 0 { + return; + } + let str = truncate_str(str, size); + unsafe { + ptr::copy_nonoverlapping( + str.as_ptr(), + self.array.as_mut_ptr().add(self.len()), + str.len(), + ); + } + self.size += str.len() as u8; } /// Inserts character to the end of the `ArrayString` erroring if total size if bigger than [`capacity`]. @@ -647,7 +532,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = ArrayString::<23>::try_from_str("My String")?; /// s.try_push('!')?; /// assert_eq!(s.as_str(), "My String!"); @@ -658,41 +543,10 @@ where /// # } /// ``` #[inline] - pub fn try_push(&mut self, character: char) -> Result<(), OutOfBounds> { + pub fn try_push(&mut self, ch: char) -> Result<(), OutOfBounds> { trace!("Push: {}", character); - let new_end = character.len_utf8().saturating_add(self.len().into()); - is_inside_boundary(new_end, Self::capacity())?; - unsafe { self.push_unchecked(character) }; - Ok(()) - } - - /// Inserts character to the end of the `ArrayString` assuming length is appropriate - /// - /// # Safety - /// - /// It's UB if `self.len() + character.len_utf8()` > [`capacity`] - /// - /// [`capacity`]: ./struct.ArrayString.html#method.capacity - /// - /// ```rust - /// # use arraystring::{Error, prelude::*}; - /// # fn main() -> Result<(), Error> { - /// let mut s = ArrayString::<23>::try_from_str("My String")?; - /// unsafe { s.push_unchecked('!') }; - /// assert_eq!(s.as_str(), "My String!"); - /// - /// // s = ArrayString::<23>::try_from_str(&"0".repeat(ArrayString::<23>::capacity().into()))?; - /// // Undefined behavior, don't do it - /// // s.push_unchecked('!'); - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub unsafe fn push_unchecked(&mut self, ch: char) { - let (len, chlen) = (self.len(), ch.len_utf8().into_lossy()); - debug!("Push unchecked (len: {}): {} (len: {})", len, ch, chlen); - encode_char_utf8_unchecked(self, ch, len); - self.size = self.size.saturating_add(chlen); + let mut buf = [0; 4]; + self.try_push_str(ch.encode_utf8(&mut buf)) } /// Truncates `ArrayString` to specified size (if smaller than current size and a valid utf-8 char index). @@ -700,7 +554,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = ArrayString::<23>::try_from_str("My String")?; /// s.truncate(5)?; /// assert_eq!(s.as_str(), "My St"); @@ -716,10 +570,10 @@ where /// # } /// ``` #[inline] - pub fn truncate(&mut self, size: u8) -> Result<(), Utf8> { + pub fn truncate(&mut self, size: usize) -> Result<(), Utf8> { debug!("Truncate: {}", size); let len = min(self.len(), size); - is_char_boundary(self, len).map(|()| self.size = len) + is_char_boundary(self, len).map(|()| self.size = len as u8) } /// Removes last character from `ArrayString`, if any. @@ -727,7 +581,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = ArrayString::<23>::try_from_str("A🤔")?; /// assert_eq!(s.pop(), Some('🤔')); /// assert_eq!(s.pop(), Some('A')); @@ -744,12 +598,12 @@ where }) } - /// Removes spaces from the beggining and end of the string + /// Removes whitespaces from the beggining and end of the string /// /// ```rust /// # use arraystring::prelude::*; /// # fn main() -> Result<(), OutOfBounds> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut string = ArrayString::<255>::try_from_str(" to be trimmed ")?; /// string.trim(); /// assert_eq!(string.as_str(), "to be trimmed"); @@ -763,29 +617,27 @@ where #[inline] pub fn trim(&mut self) { trace!("Trim"); - let is_whitespace = |s: &[u8], index: u8| { - debug_assert!((index as usize) < s.len()); - unsafe { s.get_unchecked(index as usize) == &b' ' } - }; - let (mut start, mut end, mut leave) = (0_u8, self.len(), 0_u8); - while start < end && leave < 2 { - leave = 0; - - if is_whitespace(self.as_bytes(), start) { - start += 1; - } else { - leave += 1; + let mut start = self.len(); + for (pos, char) in self.as_str().char_indices() { + if !char.is_whitespace() { + start = pos; + break; } - - if start < end && is_whitespace(self.as_bytes(), end - 1) { - end -= 1; - } else { - leave += 1; + } + let mut end = self.len(); + for (pos, char) in self.as_str().char_indices().rev() { + if pos < start { + self.size = 0; + return; + } + if !char.is_whitespace() { + break; } + end = pos; } - - unsafe { shift_left_unchecked(self, start, 0u8) }; - self.size = end.saturating_sub(start); + let range = start..end; + self.size = range.clone().count() as u8; + self.array.copy_within(range, 0); } /// Removes specified char from `ArrayString` @@ -793,9 +645,9 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = ArrayString::<23>::try_from_str("ABCD🤔")?; - /// assert_eq!(s.remove("ABCD🤔".len() as u8), Err(Error::OutOfBounds)); + /// assert_eq!(s.remove("ABCD🤔".len()), Err(Error::OutOfBounds)); /// assert_eq!(s.remove(10), Err(Error::OutOfBounds)); /// assert_eq!(s.remove(6), Err(Error::Utf8)); /// assert_eq!(s.remove(0), Ok('A')); @@ -806,16 +658,23 @@ where /// # } /// ``` #[inline] - pub fn remove(&mut self, idx: u8) -> Result { + pub fn remove(&mut self, idx: usize) -> Result { debug!("Remove: {}", idx); - is_inside_boundary(idx.saturating_add(1), self.len())?; + is_inside_boundary(idx, self.len())?; is_char_boundary(self, idx)?; - debug_assert!(idx < self.len() && self.as_str().is_char_boundary(idx.into())); - let ch = unsafe { self.as_str().get_unchecked(idx.into()..).chars().next() }; - let ch = ch.unwrap_or_else(|| unsafe { never("Missing char") }); - unsafe { shift_left_unchecked(self, idx.saturating_add(ch.len_utf8().into_lossy()), idx) }; - self.size = self.size.saturating_sub(ch.len_utf8().into_lossy()); - Ok(ch) + let char = unsafe { + self.as_str() + .get_unchecked(idx..) + .chars() + .next() + .ok_or(OutOfBounds)? + }; + let len = char.len_utf8(); + if idx + len < self.len() { + self.array.copy_within(idx + len.., idx); + } + self.size -= len as u8; + Ok(char) } /// Retains only the characters specified by the predicate. @@ -823,7 +682,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = ArrayString::<23>::try_from_str("ABCD🤔")?; /// s.retain(|c| c != '🤔'); /// assert_eq!(s.as_str(), "ABCD"); @@ -834,7 +693,7 @@ where pub fn retain(&mut self, mut f: impl FnMut(char) -> bool) { trace!("Retain"); // Not the most efficient solution, we could shift left during batch mismatch - *self = unsafe { Self::from_chars_unchecked(self.as_str().chars().filter(|c| f(*c))) }; + *self = Self::from_chars_truncate(self.as_str().chars().filter(|c| f(*c))); } /// Inserts character at specified index, returning error if total length is bigger than [`capacity`]. @@ -848,7 +707,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = ArrayString::<23>::try_from_str("ABCD🤔")?; /// s.try_insert(1, 'A')?; /// s.try_insert(2, 'B')?; @@ -862,47 +721,9 @@ where /// # } /// ``` #[inline] - pub fn try_insert(&mut self, idx: u8, ch: char) -> Result<(), Error> { - trace!("Insert {} to {}", ch, idx); - is_inside_boundary(idx, self.len())?; - let new_end = ch.len_utf8().saturating_add(self.len().into()); - is_inside_boundary(new_end, Self::capacity())?; - is_char_boundary(self, idx)?; - unsafe { self.insert_unchecked(idx, ch) }; - Ok(()) - } - - /// Inserts character at specified index assuming length is appropriate - /// - /// # Safety - /// - /// It's UB if `idx` does not lie on a utf-8 `char` boundary - /// - /// It's UB if `self.len() + character.len_utf8()` > [`capacity`] - /// - /// [`capacity`]: ./struct.ArrayString.html#method.capacity - /// - /// ```rust - /// # use arraystring::{Error, prelude::*}; - /// # fn main() -> Result<(), Error> { - /// let mut s = ArrayString::<23>::try_from_str("ABCD🤔")?; - /// unsafe { s.insert_unchecked(1, 'A') }; - /// unsafe { s.insert_unchecked(1, 'B') }; - /// assert_eq!(s.as_str(), "ABABCD🤔"); - /// - /// // Undefined behavior, don't do it - /// // s.insert(20, 'C'); - /// // s.insert(8, 'D'); - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub unsafe fn insert_unchecked(&mut self, idx: u8, ch: char) { - let clen = ch.len_utf8().into_lossy(); - debug!("Insert uncheck ({}+{clen}) {ch} at {idx}", self.len()); - shift_right_unchecked(self, idx, idx.saturating_add(clen)); - encode_char_utf8_unchecked(self, ch, idx); - self.size = self.size.saturating_add(clen); + pub fn try_insert(&mut self, idx: usize, ch: char) -> Result<(), Error> { + let mut buf = [0; 4]; + self.try_insert_str(idx, ch.encode_utf8(&mut buf)) } /// Inserts string slice at specified index, returning error if total length is bigger than [`capacity`]. @@ -917,7 +738,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = ArrayString::<23>::try_from_str("ABCD🤔")?; /// s.try_insert_str(1, "AB")?; /// s.try_insert_str(1, "BC")?; @@ -930,13 +751,19 @@ where /// # } /// ``` #[inline] - pub fn try_insert_str(&mut self, idx: u8, s: impl AsRef) -> Result<(), Error> { - trace!("Try insert at {idx} str: {}", s.as_ref()); + pub fn try_insert_str(&mut self, idx: usize, string: impl AsRef) -> Result<(), Error> { + trace!("Try insert at {idx} str: {}", string.as_ref()); + let str = string.as_ref().as_bytes(); is_inside_boundary(idx, self.len())?; - let new_end = s.as_ref().len().saturating_add(self.len().into()); - is_inside_boundary(new_end, Self::capacity())?; + is_inside_boundary(str.len() + self.len(), Self::capacity())?; is_char_boundary(self, idx)?; - unsafe { self.insert_str_unchecked(idx, s.as_ref()) }; + if str.len() == 0 { + return Ok(()); + } + let this_len = self.len(); + self.array.copy_within(idx..this_len, idx + str.len()); + self.array[idx..idx + str.len()].copy_from_slice(str); + self.size += str.len() as u8; Ok(()) } @@ -951,7 +778,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = ArrayString::<23>::try_from_str("ABCD🤔")?; /// s.insert_str_truncate(1, "AB")?; /// s.insert_str_truncate(1, "BC")?; @@ -967,61 +794,38 @@ where /// # } /// ``` #[inline] - pub fn insert_str_truncate(&mut self, idx: u8, string: impl AsRef) -> Result<(), Error> { + pub fn insert_str_truncate( + &mut self, + idx: usize, + string: impl AsRef, + ) -> Result<(), Error> { trace!("Insert str at {idx}: {}", string.as_ref()); is_inside_boundary(idx, self.len())?; is_char_boundary(self, idx)?; - let size = Self::capacity().saturating_sub(self.len()); - unsafe { self.insert_str_unchecked(idx, truncate_str(string.as_ref(), size.into())) }; + let size = Self::capacity() - idx; + if size == 0 { + return Ok(()); + } + let str = truncate_str(string.as_ref().as_bytes(), size.into()); + if str.len() == 0 { + return Ok(()); + } + let remaining = usize::min(size - str.len(), self.len() - idx); + if remaining > 0 { + self.array + .copy_within(idx..(idx + remaining), idx + str.len()) + } + self.array[idx..idx + str.len()].copy_from_slice(str); + self.size = (idx + str.len() + remaining) as u8; Ok(()) } - /// Inserts string slice at specified index, assuming total length is appropriate. - /// - /// # Safety - /// - /// It's UB if `idx` does not lie on a utf-8 `char` boundary - /// - /// It's UB if `self.len() + string.len()` > [`capacity`] - /// - /// [`capacity`]: ./struct.ArrayString.html#method.capacity - /// - /// ```rust - /// # use arraystring::{Error, prelude::*}; - /// # fn main() -> Result<(), Error> { - /// let mut s = ArrayString::<23>::try_from_str("ABCD🤔")?; - /// unsafe { s.insert_str_unchecked(1, "AB") }; - /// unsafe { s.insert_str_unchecked(1, "BC") }; - /// assert_eq!(s.as_str(), "ABCABBCD🤔"); - /// - /// // Undefined behavior, don't do it - /// // unsafe { s.insert_str_unchecked(20, "C") }; - /// // unsafe { s.insert_str_unchecked(10, "D") }; - /// // unsafe { s.insert_str_unchecked(1, "0".repeat(ArrayString::<23>::capacity().into())) }; - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub unsafe fn insert_str_unchecked(&mut self, idx: u8, string: impl AsRef) { - let (s, slen) = (string.as_ref(), string.as_ref().len().into_lossy()); - let ptr = s.as_ptr(); - trace!("InsertStr uncheck {}+{slen} {s} at {idx}", self.len()); - debug_assert!(self.len().saturating_add(slen) <= Self::capacity()); - debug_assert!(idx <= self.len()); - debug_assert!(self.as_str().is_char_boundary(idx.into())); - - shift_right_unchecked(self, idx, idx.saturating_add(slen)); - let dest = self.as_mut_bytes().as_mut_ptr().add(idx.into()); - copy_nonoverlapping(ptr, dest, slen.into()); - self.size = self.size.saturating_add(slen); - } - /// Returns `ArrayString` length. /// /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = ArrayString::<23>::try_from_str("ABCD")?; /// assert_eq!(s.len(), 4); /// s.try_push('🤔')?; @@ -1031,9 +835,9 @@ where /// # } /// ``` #[inline] - pub fn len(&self) -> u8 { + pub fn len(&self) -> usize { trace!("Len"); - self.size + self.size as usize } /// Checks if `ArrayString` is empty. @@ -1041,7 +845,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = ArrayString::<23>::try_from_str("ABCD")?; /// assert!(!s.is_empty()); /// s.clear(); @@ -1065,7 +869,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = ArrayString::<23>::try_from_str("AB🤔CD")?; /// assert_eq!(s.split_off(6)?.as_str(), "CD"); /// assert_eq!(s.as_str(), "AB🤔"); @@ -1075,13 +879,15 @@ where /// # } /// ``` #[inline] - pub fn split_off(&mut self, at: u8) -> Result { + pub fn split_off(&mut self, at: usize) -> Result { debug!("Split off"); is_inside_boundary(at, self.len())?; is_char_boundary(self, at)?; debug_assert!(at <= self.len() && self.as_str().is_char_boundary(at.into())); - let new = unsafe { Self::from_utf8_unchecked(self.as_str().get_unchecked(at.into()..)) }; - self.size = at; + let new = unsafe { + Self::try_from_str(self.as_str().get_unchecked(at.into()..)).unwrap_unchecked() + }; + self.size = at as u8; Ok(new) } @@ -1090,7 +896,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = ArrayString::<23>::try_from_str("ABCD")?; /// assert!(!s.is_empty()); /// s.clear(); @@ -1111,7 +917,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = ArrayString::<23>::try_from_str("ABCD🤔")?; /// assert_eq!(s.drain(..3)?.collect::>(), vec!['A', 'B', 'C']); /// assert_eq!(s.as_str(), "D🤔"); @@ -1122,7 +928,7 @@ where /// # } /// ``` #[inline] - pub fn drain(&mut self, range: impl RangeBounds) -> Result, Error> { + pub fn drain(&mut self, range: impl RangeBounds) -> Result, Error> { let start = match range.start_bound() { Bound::Included(t) => *t, Bound::Excluded(t) => t.saturating_add(1), @@ -1145,10 +951,10 @@ where let drain = unsafe { let slice = self.as_str().get_unchecked(start.into()..end.into()); - Self::from_str_unchecked(slice) + Self::try_from_str(slice).unwrap_unchecked() }; - unsafe { shift_left_unchecked(self, end, start) }; - self.size = self.size.saturating_sub(end.saturating_sub(start)); + self.array.copy_within(end.., start); + self.size = self.size.saturating_sub(end.saturating_sub(start) as u8); Ok(Drain(drain)) } @@ -1157,7 +963,7 @@ where /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = ArrayString::<23>::try_from_str("ABCD🤔")?; /// s.replace_range(2..4, "EFGHI")?; /// assert_eq!(s.as_str(), "ABEFGHI🤔"); @@ -1172,10 +978,10 @@ where #[inline] pub fn replace_range( &mut self, - r: impl RangeBounds, + r: impl RangeBounds, with: impl AsRef, ) -> Result<(), Error> { - let replace_with = with.as_ref(); + let str = with.as_ref().as_bytes(); let start = match r.start_bound() { Bound::Included(t) => *t, Bound::Excluded(t) => t.saturating_add(1), @@ -1186,40 +992,30 @@ where Bound::Excluded(t) => *t, Bound::Unbounded => self.len(), }; - - let len = replace_with.len().into_lossy(); debug!( "Replace range (len: {}) ({}..{}) with (len: {}) {}", self.len(), start, end, - len, - replace_with + with.as_ref().len(), + with.as_ref() ); - + if start == end && str.len() == 0 { + return Ok(()); + } is_inside_boundary(start, end)?; is_inside_boundary(end, self.len())?; - let replaced = (end as usize).saturating_sub(start.into()); - is_inside_boundary(replaced.saturating_add(len.into()), Self::capacity())?; is_char_boundary(self, start)?; is_char_boundary(self, end)?; - - debug_assert!(start <= end && end <= self.len()); - debug_assert!(len.saturating_sub(end).saturating_add(start) <= Self::capacity()); - debug_assert!(self.as_str().is_char_boundary(start.into())); - debug_assert!(self.as_str().is_char_boundary(end.into())); - - if start.saturating_add(len) > end { - unsafe { shift_right_unchecked(self, end, start.saturating_add(len)) }; - } else { - unsafe { shift_left_unchecked(self, end, start.saturating_add(len)) }; + is_inside_boundary(start + str.len() + self.len() - end, Self::capacity())?; + let dest = start + str.len(); + let this_len = self.len(); + self.array.copy_within(end..this_len, dest); + if str.len() > 0 { + self.array[start..start + str.len()].copy_from_slice(str); } - - let grow = len.saturating_sub(replaced.into_lossy()); - self.size = self.size.saturating_add(grow); - let ptr = replace_with.as_ptr(); - let dest = unsafe { self.as_mut_bytes().as_mut_ptr().add(start.into()) }; - unsafe { copy_nonoverlapping(ptr, dest, len.into()) }; + self.size -= (end - start) as u8; + self.size += str.len() as u8; Ok(()) } } diff --git a/src/lib.rs b/src/lib.rs index 62f0461..d24ca51 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -218,7 +218,7 @@ mod cache_string { /// /// ```rust /// # use arraystring::prelude::*; - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let string = CacheString::new(); /// assert!(string.is_empty()); /// ``` @@ -233,7 +233,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let string = CacheString::try_from_str("My String")?; /// assert_eq!(string.as_str(), "My String"); /// @@ -255,7 +255,7 @@ mod cache_string { /// /// ```rust /// # use arraystring::prelude::*; - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let string = CacheString::from_str_truncate("My String"); /// # assert_eq!(string.as_str(), "My String"); /// println!("{}", string); @@ -270,31 +270,6 @@ mod cache_string { Self(ArrayString::from_str_truncate(string)) } - /// Creates new `CacheString` from string slice assuming length is appropriate. - /// - /// # Safety - /// - /// It's UB if `string.len()` > [`capacity`]. - /// - /// [`capacity`]: ./struct.CacheString.html#method.capacity - /// - /// ```rust - /// # use arraystring::prelude::*; - /// let filled = "0".repeat(CacheString::capacity().into()); - /// let string = unsafe { - /// CacheString::from_str_unchecked(&filled) - /// }; - /// assert_eq!(string.as_str(), filled.as_str()); - /// - /// // Undefined behavior, don't do it - /// // let out_of_bounds = "0".repeat(CacheString::capacity().into() + 1); - /// // let ub = unsafe { CacheString::from_str_unchecked(out_of_bounds) }; - /// ``` - #[inline] - pub unsafe fn from_str_unchecked(string: impl AsRef) -> Self { - Self(ArrayString::from_str_unchecked(string)) - } - /// Creates new `CacheString` from string slice iterator if total length is lower or equal to [`capacity`], otherwise returns an error. /// /// [`capacity`]: ./struct.CacheString.html#method.capacity @@ -324,7 +299,7 @@ mod cache_string { /// ```rust /// # use arraystring::prelude::*; /// # fn main() -> Result<(), OutOfBounds> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let string = CacheString::from_iterator_truncate(&["My String", " Other String"][..]); /// assert_eq!(string.as_str(), "My String Other String"); /// @@ -341,34 +316,6 @@ mod cache_string { Self(ArrayString::from_iterator_truncate(iter)) } - /// Creates new `CacheString` from string slice iterator assuming length is appropriate. - /// - /// # Safety - /// - /// It's UB if `iter.map(|c| c.len()).sum()` > [`capacity`]. - /// - /// [`capacity`]: ./struct.CacheString.html#method.capacity - /// - /// ```rust - /// # use arraystring::prelude::*; - /// let string = unsafe { - /// CacheString::from_iterator_unchecked(&["My String", " My Other String"][..]) - /// }; - /// assert_eq!(string.as_str(), "My String My Other String"); - /// - /// // Undefined behavior, don't do it - /// // let out_of_bounds = (0..400).map(|_| "000"); - /// // let undefined_behavior = unsafe { - /// // CacheString::from_iterator_unchecked(out_of_bounds) - /// // }; - /// ``` - #[inline] - pub unsafe fn from_iterator_unchecked( - iter: impl IntoIterator>, - ) -> Self { - Self(ArrayString::from_iterator_unchecked(iter)) - } - /// Creates new `CacheString` from char iterator if total length is lower or equal to [`capacity`], otherwise returns an error. /// /// [`capacity`]: ./struct.CacheString.html#method.capacity @@ -376,7 +323,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let string = CacheString::try_from_chars("My String".chars())?; /// assert_eq!(string.as_str(), "My String"); /// @@ -396,7 +343,7 @@ mod cache_string { /// /// ```rust /// # use arraystring::prelude::*; - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let string = CacheString::from_chars_truncate("My String".chars()); /// assert_eq!(string.as_str(), "My String"); /// @@ -411,28 +358,6 @@ mod cache_string { Self(ArrayString::from_chars_truncate(iter)) } - /// Creates new `CacheString` from char iterator assuming length is appropriate. - /// - /// # Safety - /// - /// It's UB if `iter.map(|c| c.len_utf8()).sum()` > [`capacity`]. - /// - /// [`capacity`]: ./struct.CacheString.html#method.capacity - /// - /// ```rust - /// # use arraystring::prelude::*; - /// let string = unsafe { CacheString::from_chars_unchecked("My String".chars()) }; - /// assert_eq!(string.as_str(), "My String"); - /// - /// // Undefined behavior, don't do it - /// // let out_of_bounds = "000".repeat(400); - /// // let undefined_behavior = unsafe { CacheString::from_chars_unchecked(out_of_bounds.chars()) }; - /// ``` - #[inline] - pub unsafe fn from_chars_unchecked(iter: impl IntoIterator) -> Self { - Self(ArrayString::from_chars_unchecked(iter)) - } - /// Creates new `CacheString` from byte slice, returning [`Utf8`] on invalid utf-8 data or [`OutOfBounds`] if bigger than [`capacity`] /// /// [`Utf8`]: ./error/enum.Error.html#variant.Utf8 @@ -442,7 +367,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let string = CacheString::try_from_utf8("My String")?; /// assert_eq!(string.as_str(), "My String"); /// @@ -467,7 +392,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let string = CacheString::from_utf8_truncate("My String")?; /// assert_eq!(string.as_str(), "My String"); /// @@ -485,28 +410,6 @@ mod cache_string { Ok(Self(ArrayString::from_utf8_truncate(slice)?)) } - /// Creates new `CacheString` from byte slice assuming it's utf-8 and of a appropriate size. - /// - /// # Safety - /// - /// It's UB if `slice` is not a valid utf-8 string or `slice.len()` > [`capacity`]. - /// - /// [`capacity`]: ./struct.CacheString.html#method.capacity - /// - /// ```rust - /// # use arraystring::prelude::*; - /// let string = unsafe { CacheString::from_utf8_unchecked("My String") }; - /// assert_eq!(string.as_str(), "My String"); - /// - /// // Undefined behavior, don't do it - /// // let out_of_bounds = "0".repeat(300); - /// // let ub = unsafe { CacheString::from_utf8_unchecked(out_of_bounds)) }; - /// ``` - #[inline] - pub unsafe fn from_utf8_unchecked(slice: impl AsRef<[u8]>) -> Self { - Self(ArrayString::from_utf8_unchecked(slice)) - } - /// Creates new `CacheString` from `u16` slice, returning [`Utf16`] on invalid utf-16 data or [`OutOfBounds`] if bigger than [`capacity`] /// /// [`Utf16`]: ./error/enum.Error.html#variant.Utf16 @@ -516,7 +419,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let music = [0xD834, 0xDD1E, 0x006d, 0x0075, 0x0073, 0x0069, 0x0063]; /// let string = CacheString::try_from_utf16(music)?; /// assert_eq!(string.as_str(), "𝄞music"); @@ -542,7 +445,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let music = [0xD834, 0xDD1E, 0x006d, 0x0075, 0x0073, 0x0069, 0x0063]; /// let string = CacheString::from_utf16_truncate(music)?; /// assert_eq!(string.as_str(), "𝄞music"); @@ -568,7 +471,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let music = [0xD834, 0xDD1E, 0x006d, 0x0075, 0x0073, 0x0069, 0x0063]; /// let string = CacheString::from_utf16_lossy_truncate(music); /// assert_eq!(string.as_str(), "𝄞music"); @@ -592,7 +495,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let s = CacheString::try_from_str("My String")?; /// assert_eq!(s.as_str(), "My String"); /// # Ok(()) @@ -608,7 +511,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = CacheString::try_from_str("My String")?; /// assert_eq!(s.as_mut_str(), "My String"); /// # Ok(()) @@ -624,7 +527,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let s = CacheString::try_from_str("My String")?; /// assert_eq!(s.as_bytes(), "My String".as_bytes()); /// # Ok(()) @@ -660,7 +563,7 @@ mod cache_string { /// /// ```rust /// # use arraystring::prelude::*; - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// assert_eq!(CacheString::capacity(), 63); /// ``` #[inline] @@ -675,7 +578,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = CacheString::try_from_str("My String")?; /// s.try_push_str(" My other String")?; /// assert_eq!(s.as_str(), "My String My other String"); @@ -696,7 +599,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = CacheString::try_from_str("My String")?; /// s.push_str_truncate(" My other String"); /// assert_eq!(s.as_str(), "My String My other String"); @@ -712,32 +615,6 @@ mod cache_string { self.0.push_str_truncate(string); } - /// Pushes string slice to the end of the `CacheString` assuming total size is appropriate. - /// - /// # Safety - /// - /// It's UB if `self.len() + string.len()` > [`capacity`]. - /// - /// [`capacity`]: ./struct.CacheString.html#method.capacity - /// - /// ```rust - /// # use arraystring::{Error, prelude::*}; - /// # fn main() -> Result<(), Error> { - /// let mut s = CacheString::try_from_str("My String")?; - /// unsafe { s.push_str_unchecked(" My other String") }; - /// assert_eq!(s.as_str(), "My String My other String"); - /// - /// // Undefined behavior, don't do it - /// // let mut undefined_behavior = CacheString::default(); - /// // undefined_behavior.push_str_unchecked("0".repeat(CacheString::capacity().into() + 1)); - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub unsafe fn push_str_unchecked(&mut self, string: impl AsRef) { - self.0.push_str_unchecked(string); - } - /// Inserts character to the end of the `CacheString` erroring if total size if bigger than [`capacity`]. /// /// [`capacity`]: ./struct.CacheString.html#method.capacity @@ -745,7 +622,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = CacheString::try_from_str("My String")?; /// s.try_push('!')?; /// assert_eq!(s.as_str(), "My String!"); @@ -760,38 +637,12 @@ mod cache_string { self.0.try_push(character) } - /// Inserts character to the end of the `CacheString` assuming length is appropriate - /// - /// # Safety - /// - /// It's UB if `self.len() + character.len_utf8()` > [`capacity`] - /// - /// [`capacity`]: ./struct.ArrayString.html#method.capacity - /// - /// ```rust - /// # use arraystring::{Error, prelude::*}; - /// # fn main() -> Result<(), Error> { - /// let mut s = CacheString::try_from_str("My String")?; - /// unsafe { s.push_unchecked('!') }; - /// assert_eq!(s.as_str(), "My String!"); - /// - /// // s = CacheString::try_from_str(&"0".repeat(CacheString::capacity().into()))?; - /// // Undefined behavior, don't do it - /// // s.push_unchecked('!'); - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub unsafe fn push_unchecked(&mut self, ch: char) { - self.0.push_unchecked(ch); - } - /// Truncates `CacheString` to specified size (if smaller than current size and a valid utf-8 char index). /// /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = CacheString::try_from_str("My String")?; /// s.truncate(5)?; /// assert_eq!(s.as_str(), "My St"); @@ -807,7 +658,7 @@ mod cache_string { /// # } /// ``` #[inline] - pub fn truncate(&mut self, size: u8) -> Result<(), Utf8> { + pub fn truncate(&mut self, size: usize) -> Result<(), Utf8> { self.0.truncate(size) } @@ -816,7 +667,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = CacheString::try_from_str("A🤔")?; /// assert_eq!(s.pop(), Some('🤔')); /// assert_eq!(s.pop(), Some('A')); @@ -834,7 +685,7 @@ mod cache_string { /// ```rust /// # use arraystring::prelude::*; /// # fn main() -> Result<(), OutOfBounds> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut string = CacheString::try_from_str(" to be trimmed ")?; /// string.trim(); /// assert_eq!(string.as_str(), "to be trimmed"); @@ -855,9 +706,9 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = CacheString::try_from_str("ABCD🤔")?; - /// assert_eq!(s.remove("ABCD🤔".len() as u8), Err(Error::OutOfBounds)); + /// assert_eq!(s.remove("ABCD🤔".len()), Err(Error::OutOfBounds)); /// assert_eq!(s.remove(10), Err(Error::OutOfBounds)); /// assert_eq!(s.remove(6), Err(Error::Utf8)); /// assert_eq!(s.remove(0), Ok('A')); @@ -868,7 +719,7 @@ mod cache_string { /// # } /// ``` #[inline] - pub fn remove(&mut self, idx: u8) -> Result { + pub fn remove(&mut self, idx: usize) -> Result { self.0.remove(idx) } @@ -877,7 +728,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = CacheString::try_from_str("ABCD🤔")?; /// s.retain(|c| c != '🤔'); /// assert_eq!(s.as_str(), "ABCD"); @@ -900,7 +751,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = CacheString::try_from_str("ABCD🤔")?; /// s.try_insert(1, 'A')?; /// s.try_insert(2, 'B')?; @@ -914,39 +765,10 @@ mod cache_string { /// # } /// ``` #[inline] - pub fn try_insert(&mut self, idx: u8, ch: char) -> Result<(), Error> { + pub fn try_insert(&mut self, idx: usize, ch: char) -> Result<(), Error> { self.0.try_insert(idx, ch) } - /// Inserts character at specified index assuming length is appropriate - /// - /// # Safety - /// - /// It's UB if `idx` does not lie on a utf-8 `char` boundary - /// - /// It's UB if `self.len() + character.len_utf8()` > [`capacity`] - /// - /// [`capacity`]: ./struct.CacheString.html#method.capacity - /// - /// ```rust - /// # use arraystring::{Error, prelude::*}; - /// # fn main() -> Result<(), Error> { - /// let mut s = CacheString::try_from_str("ABCD🤔")?; - /// unsafe { s.insert_unchecked(1, 'A') }; - /// unsafe { s.insert_unchecked(1, 'B') }; - /// assert_eq!(s.as_str(), "ABABCD🤔"); - /// - /// // Undefined behavior, don't do it - /// // s.insert(20, 'C'); - /// // s.insert(8, 'D'); - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub unsafe fn insert_unchecked(&mut self, idx: u8, ch: char) { - self.0.insert_unchecked(idx, ch) - } - /// Inserts string slice at specified index, returning error if total length is bigger than [`capacity`]. /// /// Returns [`OutOfBounds`] if `idx` is out of bounds @@ -959,7 +781,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = CacheString::try_from_str("ABCD🤔")?; /// s.try_insert_str(1, "AB")?; /// s.try_insert_str(1, "BC")?; @@ -972,7 +794,7 @@ mod cache_string { /// # } /// ``` #[inline] - pub fn try_insert_str(&mut self, idx: u8, s: impl AsRef) -> Result<(), Error> { + pub fn try_insert_str(&mut self, idx: usize, s: impl AsRef) -> Result<(), Error> { self.0.try_insert_str(idx, s) } @@ -987,7 +809,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = CacheString::try_from_str("ABCD🤔")?; /// s.insert_str_truncate(1, "AB")?; /// s.insert_str_truncate(1, "BC")?; @@ -1005,48 +827,18 @@ mod cache_string { #[inline] pub fn insert_str_truncate( &mut self, - idx: u8, + idx: usize, string: impl AsRef, ) -> Result<(), Error> { self.0.insert_str_truncate(idx, string) } - /// Inserts string slice at specified index, assuming total length is appropriate. - /// - /// # Safety - /// - /// It's UB if `idx` does not lie on a utf-8 `char` boundary - /// - /// It's UB if `self.len() + string.len()` > [`capacity`] - /// - /// [`capacity`]: ./struct.CacheString.html#method.capacity - /// - /// ```rust - /// # use arraystring::{Error, prelude::*}; - /// # fn main() -> Result<(), Error> { - /// let mut s = CacheString::try_from_str("ABCD🤔")?; - /// unsafe { s.insert_str_unchecked(1, "AB") }; - /// unsafe { s.insert_str_unchecked(1, "BC") }; - /// assert_eq!(s.as_str(), "ABCABBCD🤔"); - /// - /// // Undefined behavior, don't do it - /// // unsafe { s.insert_str_unchecked(20, "C") }; - /// // unsafe { s.insert_str_unchecked(10, "D") }; - /// // unsafe { s.insert_str_unchecked(1, "0".repeat(CacheString::capacity().into())) }; - /// # Ok(()) - /// # } - /// ``` - #[inline] - pub unsafe fn insert_str_unchecked(&mut self, idx: u8, string: impl AsRef) { - self.0.insert_str_unchecked(idx, string) - } - /// Returns `CacheString` length. /// /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = CacheString::try_from_str("ABCD")?; /// assert_eq!(s.len(), 4); /// s.try_push('🤔')?; @@ -1056,7 +848,7 @@ mod cache_string { /// # } /// ``` #[inline] - pub fn len(&self) -> u8 { + pub fn len(&self) -> usize { self.0.len() } @@ -1065,7 +857,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = CacheString::try_from_str("ABCD")?; /// assert!(!s.is_empty()); /// s.clear(); @@ -1088,7 +880,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = CacheString::try_from_str("AB🤔CD")?; /// assert_eq!(s.split_off(6)?.as_str(), "CD"); /// assert_eq!(s.as_str(), "AB🤔"); @@ -1098,7 +890,7 @@ mod cache_string { /// # } /// ``` #[inline] - pub fn split_off(&mut self, at: u8) -> Result { + pub fn split_off(&mut self, at: usize) -> Result { Ok(Self(self.0.split_off(at)?)) } @@ -1107,7 +899,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = CacheString::try_from_str("ABCD")?; /// assert!(!s.is_empty()); /// s.clear(); @@ -1127,7 +919,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = CacheString::try_from_str("ABCD🤔")?; /// assert_eq!(s.drain(..3)?.collect::>(), vec!['A', 'B', 'C']); /// assert_eq!(s.as_str(), "D🤔"); @@ -1140,7 +932,7 @@ mod cache_string { #[inline] pub fn drain( &mut self, - range: impl RangeBounds, + range: impl RangeBounds, ) -> Result, Error> { self.0.drain(range) } @@ -1150,7 +942,7 @@ mod cache_string { /// ```rust /// # use arraystring::{Error, prelude::*}; /// # fn main() -> Result<(), Error> { - /// # let _ = env_logger::try_init(); + /// # #[cfg(not(miri))] let _ = env_logger::try_init(); /// let mut s = CacheString::try_from_str("ABCD🤔")?; /// s.replace_range(2..4, "EFGHI")?; /// assert_eq!(s.as_str(), "ABEFGHI🤔"); @@ -1165,7 +957,7 @@ mod cache_string { #[inline] pub fn replace_range( &mut self, - r: impl RangeBounds, + r: impl RangeBounds, with: impl AsRef, ) -> Result<(), Error> { self.0.replace_range(r, with) diff --git a/src/utils.rs b/src/utils.rs index dc0e484..a78f98a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,187 +1,69 @@ //! Misc functions to improve readability use crate::{arraystring::sealed::ValidCapacity, prelude::*}; -use core::ptr::copy; #[cfg(feature = "logs")] use log::{debug, trace}; +use no_panic::no_panic; pub(crate) trait IntoLossy: Sized { fn into_lossy(self) -> T; } -/// Marks branch as impossible, UB if taken in prod, panics in debug -/// -/// This function should never be used lightly, it will cause UB if used wrong -#[inline] -#[allow(unused_variables)] -pub(crate) unsafe fn never(s: &str) -> ! { - #[cfg(debug_assertions)] - panic!("{}", s); - - #[cfg(not(debug_assertions))] - core::hint::unreachable_unchecked() -} - -/// Encodes `char` into `ArrayString` at specified position, heavily unsafe -/// -/// We reimplement the `core` function to avoid panicking (UB instead, be careful) -/// -/// Reimplemented from: -/// -/// `https://github.com/rust-lang/rust/blob/7843e2792dce0f20d23b3c1cca51652013bef0ea/src/libcore/char/methods.rs#L447` -/// # Safety -/// -/// - It's UB if index is outside of buffer's boundaries (buffer needs at most 4 bytes) -/// - It's UB if index is inside a character (like a index 3 for "a🤔") -#[inline] -pub(crate) unsafe fn encode_char_utf8_unchecked( - s: &mut ArrayString, - ch: char, - index: u8, -) where - ArrayString: ValidCapacity, -{ - // UTF-8 ranges and tags for encoding characters - #[allow(clippy::missing_docs_in_private_items)] - const TAG_CONT: u8 = 0b1000_0000; - #[allow(clippy::missing_docs_in_private_items)] - const TAG_TWO_B: u8 = 0b1100_0000; - #[allow(clippy::missing_docs_in_private_items)] - const TAG_THREE_B: u8 = 0b1110_0000; - #[allow(clippy::missing_docs_in_private_items)] - const TAG_FOUR_B: u8 = 0b1111_0000; - #[allow(clippy::missing_docs_in_private_items)] - const MAX_ONE_B: u32 = 0x80; - #[allow(clippy::missing_docs_in_private_items)] - const MAX_TWO_B: u32 = 0x800; - #[allow(clippy::missing_docs_in_private_items)] - const MAX_THREE_B: u32 = 0x10000; - - trace!("Encode char: {} to {}", ch, index); - - debug_assert!(ch.len_utf8().saturating_add(index.into()) <= N); - debug_assert!(ch.len_utf8().saturating_add(s.len().into()) <= N); - let dst = s.array.as_mut_slice().get_unchecked_mut(index.into()..); - let code = ch as u32; - - if code < MAX_ONE_B { - debug_assert!(!dst.is_empty()); - *dst.get_unchecked_mut(0) = code.into_lossy(); - } else if code < MAX_TWO_B { - debug_assert!(dst.len() >= 2); - *dst.get_unchecked_mut(0) = (code >> 6 & 0x1F).into_lossy() | TAG_TWO_B; - *dst.get_unchecked_mut(1) = (code & 0x3F).into_lossy() | TAG_CONT; - } else if code < MAX_THREE_B { - debug_assert!(dst.len() >= 3); - *dst.get_unchecked_mut(0) = (code >> 12 & 0x0F).into_lossy() | TAG_THREE_B; - *dst.get_unchecked_mut(1) = (code >> 6 & 0x3F).into_lossy() | TAG_CONT; - *dst.get_unchecked_mut(2) = (code & 0x3F).into_lossy() | TAG_CONT; - } else { - debug_assert!(dst.len() >= 4); - *dst.get_unchecked_mut(0) = (code >> 18 & 0x07).into_lossy() | TAG_FOUR_B; - *dst.get_unchecked_mut(1) = (code >> 12 & 0x3F).into_lossy() | TAG_CONT; - *dst.get_unchecked_mut(2) = (code >> 6 & 0x3F).into_lossy() | TAG_CONT; - *dst.get_unchecked_mut(3) = (code & 0x3F).into_lossy() | TAG_CONT; - } -} - -/// Copies part of slice to another part (`mem::copy`, basically `memmove`) -#[inline] -unsafe fn shift_unchecked(s: &mut [u8], from: usize, to: usize, len: usize) { - debug!( - "Shift {:?} {}-{}", - &s.get(from..).map(|s| s.get(..len)), - from, - to - ); - debug_assert!(to.saturating_add(len) <= s.len() && from.saturating_add(len) <= s.len()); - let (f, t) = (s.as_ptr().add(from), s.as_mut_ptr().add(to)); - copy(f, t, len); -} - -/// Shifts string right -/// -/// # Safety -/// -/// It's UB if `to + (s.len() - from)` is bigger than [`S::to_u8()`] -/// -/// [`::to_u8()`]: ../struct.ArrayString.html#CAPACITY -#[inline] -pub(crate) unsafe fn shift_right_unchecked( - s: &mut ArrayString, - from: F, - to: T, -) where - ArrayString: ValidCapacity, - F: Into + Copy, - T: Into + Copy, -{ - let len = (s.len() as usize).saturating_sub(from.into()); - debug_assert!(from.into() <= to.into() && to.into().saturating_add(len) <= N); - debug_assert!(s.as_str().is_char_boundary(from.into())); - shift_unchecked(s.array.as_mut_slice(), from.into(), to.into(), len); -} - -/// Shifts string left -#[inline] -pub(crate) unsafe fn shift_left_unchecked( - s: &mut ArrayString, - from: F, - to: T, -) where - ArrayString: ValidCapacity, - F: Into + Copy, - T: Into + Copy, -{ - debug_assert!(to.into() <= from.into() && from.into() <= s.len().into()); - debug_assert!(s.as_str().is_char_boundary(from.into())); - - let len = (s.len() as usize).saturating_sub(to.into()); - shift_unchecked(s.array.as_mut_slice(), from.into(), to.into(), len); -} - /// Returns error if size is outside of specified boundary +#[cfg_attr(not(debug_assertions), no_panic)] #[inline] -pub fn is_inside_boundary(size: S, limit: L) -> Result<(), OutOfBounds> -where - S: Into, - L: Into, -{ - let (s, l) = (size.into(), limit.into()); - trace!("Out of bounds: ensures {} < {}", s, l); - Some(()).filter(|_| s <= l).ok_or(OutOfBounds) +pub fn is_inside_boundary(size: usize, limit: usize) -> Result<(), OutOfBounds> { + trace!("Out of bounds: ensures {} <= {}", size, limit); + (size <= limit).then_some(()).ok_or(OutOfBounds) } /// Returns error if index is not at a valid utf-8 char boundary #[inline] -pub fn is_char_boundary(s: &ArrayString, idx: u8) -> Result<(), Utf8> +pub fn is_char_boundary( + s: &ArrayString, + idx: impl Into, +) -> Result<(), Utf8> where ArrayString: ValidCapacity, { + let idx = idx.into(); trace!("Is char boundary: {} at {}", s.as_str(), idx); - if s.as_str().is_char_boundary(idx.into()) { + if s.as_str().is_char_boundary(idx) { return Ok(()); } Err(Utf8) } +#[inline] +pub unsafe fn is_char_boundary_at(arr: &[u8], index: usize) -> bool { + if index == 0 { + return true; + } + (*arr.get_unchecked(index) as i8) >= -0x40 +} + /// Truncates string to specified size (ignoring last bytes if they form a partial `char`) #[inline] -pub(crate) fn truncate_str(slice: &str, mut size: usize) -> &str { - trace!("Truncate str: {} at {}", slice, size); +#[cfg_attr(not(debug_assertions), no_panic)] +pub(crate) fn truncate_str(slice: &[u8], mut size: usize) -> &[u8] { + trace!( + "Truncate str: {} at {}", + unsafe { std::str::from_utf8_unchecked(slice) }, + size + ); if size >= slice.len() { return slice; } unsafe { - if slice.is_char_boundary(size) { + if is_char_boundary_at(slice, size) { return slice.get_unchecked(..size); } size -= 1; - if slice.is_char_boundary(size) { + if is_char_boundary_at(slice, size) { return slice.get_unchecked(..size); } size -= 1; - if slice.is_char_boundary(size) { + if is_char_boundary_at(slice, size) { return slice.get_unchecked(..size); } size -= 1; @@ -208,56 +90,11 @@ impl IntoLossy for u32 { #[cfg(test)] mod tests { use super::*; - use core::str::from_utf8; - - type TestString = ArrayString<23>; #[test] fn truncate() { - assert_eq!(truncate_str("i", 10), "i"); - assert_eq!(truncate_str("iiiiii", 3), "iii"); - assert_eq!(truncate_str("🤔🤔🤔", 5), "🤔"); - } - - #[test] - fn shift_right() { - let _ = env_logger::try_init(); - let mut ls = TestString::try_from_str("abcdefg").unwrap(); - unsafe { shift_right_unchecked(&mut ls, 0u8, 4u8) }; - ls.size += 4; - assert_eq!(ls.as_str(), "abcdabcdefg"); - } - - #[test] - fn shift_left() { - let _ = env_logger::try_init(); - let mut ls = TestString::try_from_str("abcdefg").unwrap(); - unsafe { shift_left_unchecked(&mut ls, 1u8, 0u8) }; - ls.size -= 1; - assert_eq!(ls.as_str(), "bcdefg"); - } - - #[test] - fn shift_nop() { - let _ = env_logger::try_init(); - let mut ls = TestString::try_from_str("abcdefg").unwrap(); - unsafe { shift_right_unchecked(&mut ls, 0u8, 0u8) }; - assert_eq!(ls.as_str(), "abcdefg"); - unsafe { shift_left_unchecked(&mut ls, 0u8, 0u8) }; - assert_eq!(ls.as_str(), "abcdefg"); - } - - #[test] - fn encode_char_utf8() { - let _ = env_logger::try_init(); - let mut string = TestString::default(); - unsafe { - encode_char_utf8_unchecked(&mut string, 'a', 0); - assert_eq!(from_utf8(&string.array.as_mut_slice()[..1]).unwrap(), "a"); - let mut string = TestString::try_from_str("a").unwrap(); - - encode_char_utf8_unchecked(&mut string, '🤔', 1); - assert_eq!(from_utf8(&string.array.as_mut_slice()[..5]).unwrap(), "a🤔"); - } + assert_eq!(truncate_str(b"i", 10), b"i"); + assert_eq!(truncate_str(b"iiiiii", 3), b"iii"); + assert_eq!(truncate_str("🤔🤔🤔".as_bytes(), 5), "🤔".as_bytes()); } } diff --git a/tests/string_parity.rs b/tests/string_parity.rs index 890b71d..bd6fbf1 100644 --- a/tests/string_parity.rs +++ b/tests/string_parity.rs @@ -1,4 +1,4 @@ -use arraystring::{error::Error, prelude::*, utils::is_char_boundary, utils::is_inside_boundary}; +use arraystring::prelude::*; use std::panic::{catch_unwind, AssertUnwindSafe, RefUnwindSafe}; use std::{fmt::Debug, iter::FromIterator}; @@ -32,13 +32,6 @@ fn from_str_truncate() { assert(String::from, TestString::from_str_truncate); } -#[test] -fn from_str_unchecked() { - assert(String::from, |s| unsafe { - TestString::from_str_unchecked(s) - }); -} - #[test] fn try_from_chars() { assert( @@ -55,14 +48,6 @@ fn from_chars() { ); } -#[test] -fn from_chars_unchecked() { - assert( - |s| String::from_iter(s.chars()), - |s| unsafe { TestString::from_chars_unchecked(s.chars()) }, - ); -} - #[test] fn try_from_iter() { assert( @@ -79,14 +64,6 @@ fn from_iter() { ); } -#[test] -fn from_iter_unchecked() { - assert( - |s| String::from_iter(vec![s]), - |s| unsafe { TestString::from_iterator_unchecked(vec![s]) }, - ); -} - #[test] fn try_from_utf8() { assert( @@ -201,16 +178,6 @@ fn from_utf16_lossy_invalid() { ); } -#[test] -fn from_utf8_unchecked() { - unsafe { - assert( - |s| String::from_utf8_unchecked(s.as_bytes().to_vec()), - |s| TestString::from_utf8_unchecked(s.as_bytes()), - ); - } -} - #[test] fn try_push_str() { assert( @@ -250,22 +217,6 @@ fn add_str() { ); } -#[test] -fn push_str_unchecked() { - assert( - |s| { - let mut st = String::from(s); - st.push_str(s); - st - }, - |s| { - let mut ms = TestString::try_from_str(s).unwrap(); - unsafe { ms.push_str_unchecked(s) }; - ms - }, - ); -} - #[test] fn push() { assert( @@ -281,22 +232,6 @@ fn push() { ); } -#[test] -fn push_unchecked() { - assert( - |s| { - let mut s = String::from(s); - s.push('🤔'); - s - }, - |s| { - let mut ms = TestString::try_from_str(s).unwrap(); - unsafe { ms.push_unchecked('🤔') }; - ms - }, - ); -} - #[test] fn truncate() { assert( @@ -392,27 +327,6 @@ fn try_insert() { ); } -#[test] -fn insert_unchecked() { - assert( - |s| { - unwind(move || { - let mut s = String::from(s); - s.insert(2, 'a'); - s - }) - }, - |s| { - let mut ms = TestString::try_from_str(s).unwrap(); - is_inside_boundary(2u8, ms.len()) - .map_err(Error::from) - .and_then(|()| Ok(is_char_boundary(&ms, 2)?)) - .map(|()| unsafe { ms.insert_unchecked(2, 'a') }) - .map(|()| ms) - }, - ); -} - #[test] fn try_insert_str() { assert( @@ -448,27 +362,6 @@ fn insert_str() { ); } -#[test] -fn insert_str_unchecked() { - assert( - |s| { - unwind(move || { - let mut st = String::from(s); - st.insert_str(2, s); - st - }) - }, - |s| { - let mut ms = TestString::try_from_str(s).unwrap(); - is_inside_boundary(2u8, ms.len()) - .map_err(Error::from) - .and_then(|()| Ok(is_char_boundary(&ms, 2)?)) - .map(|()| unsafe { ms.insert_str_unchecked(2, s) }) - .map(|()| ms) - }, - ); -} - #[test] fn clear() { assert( @@ -648,6 +541,7 @@ where F: Fn(&'static str) -> T + RefUnwindSafe, G: Fn(&'static str) -> U + RefUnwindSafe, { + #[cfg(not(miri))] let _ = env_logger::try_init(); for string in STRINGS.iter() { let f = f(string).normalize();