diff --git a/Cargo.toml b/Cargo.toml index 00efbed..fb8356f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ log = "0.4" byteorder = "1.3" ascii = { version = "1.0", features = ["serde"] } arrayref = "0.3" +contracts = "0.6" # Serde serde = { version = "1.0", features = ["derive"] } diff --git a/src/ascii_str_arr.rs b/src/ascii_str_arr.rs index 6203d1a..8a6c5a0 100644 --- a/src/ascii_str_arr.rs +++ b/src/ascii_str_arr.rs @@ -6,7 +6,8 @@ pub mod slice; mod visitor; // Exports -pub use error::{FromByteStringError, NotAsciiError, TooLongError}; +pub use error::{FromBytesError, NotAsciiError, TooLongError}; +pub use slice::SliceIndex; // Imports use ascii::{AsciiChar, AsciiStr}; @@ -20,96 +21,178 @@ pub struct AsciiStrArr { chars: [AsciiChar; N], /// Size - /// - /// Invariant: Must be `< N` + // Invariant `self.len <= N` len: usize, } +// Length interface impl AsciiStrArr { /// Returns the length of this string #[must_use] - pub const fn len(&self) -> usize { + #[contracts::debug_ensures(self.len <= N)] + pub fn len(&self) -> usize { self.len } /// Returns if this string is empty #[must_use] - pub const fn is_empty(&self) -> bool { + pub fn is_empty(&self) -> bool { self.len() == 0 } +} - /// Converts this string to a `&[AsciiStr]` +// Conversions into other strings +impl AsciiStrArr { + /// Converts this string to a `&AsciiStr` #[must_use] - pub fn as_ascii_str(&self) -> &AsciiStr { - // Note: Cannot panic due to our invariant - <&AsciiStr>::from(&self.chars[..self.len]) + pub fn as_ascii(&self) -> &AsciiStr { + // SAFETY: `self.len <= N` + let chars = unsafe { self.chars.get_unchecked(..self.len()) }; + chars.into() } - /// Converts this string to a `&mut [AsciiStr]` + /// Converts this string to a `&mut AsciiStr` #[must_use] - pub fn as_ascii_str_mut(&mut self) -> &mut AsciiStr { - // Note: Cannot panic due to our invariant - <&mut AsciiStr>::from(&mut self.chars[..self.len]) + pub fn as_ascii_mut(&mut self) -> &mut AsciiStr { + // SAFETY: `self.len <= N` + let len = self.len(); + let chars = unsafe { self.chars.get_unchecked_mut(..len) }; + chars.into() } /// Converts this string to a `&[u8]` #[must_use] pub fn as_bytes(&self) -> &[u8] { - self.as_ascii_str().as_bytes() + self.as_ascii().as_bytes() } /// Converts this string to a `&str` #[must_use] pub fn as_str(&self) -> &str { - self.as_ascii_str().as_str() + self.as_ascii().as_str() + } +} + +/// Conversions from other strings +impl AsciiStrArr { + /// Creates a string from a `&AsciiStr` + pub fn from_ascii(ascii: &AsciiStr) -> Result> { + // If we can't fit it, return Err + let len = ascii.len(); + if len > N { + return Err(TooLongError::); + } + + // Else copy everything over and return ourselves + let mut chars = [AsciiChar::Null; N]; + chars[0..len].copy_from_slice(ascii.as_ref()); + Ok(Self { chars, len }) + } + + /// Creates a string from a `&[u8]` + pub fn from_bytes(bytes: &[u8]) -> Result> { + // Get the bytes as ascii first + let ascii = AsciiStr::from_ascii(bytes).map_err(FromBytesError::NotAscii)?; + + // Then try to convert them + Self::from_ascii(ascii).map_err(FromBytesError::TooLong) + } + + // Note: No `from_str`, implemented using `FromStr` +} + +// Slicing +impl AsciiStrArr { + /// Slices this string, if in bounds + #[must_use] + pub fn get(&self, idx: I) -> Option<&I::Output> { + idx.get(self) + } + + /// Slices this string mutably, if in bounds + #[must_use] + pub fn get_mut(&mut self, idx: I) -> Option<&mut I::Output> { + idx.get_mut(self) + } + + /// Slices the string without checking bounds + /// + /// # Safety + /// Calling this method with an out-of-bounds index is undefined-behavior even if the resulting + /// reference is not used + #[must_use] + pub unsafe fn get_unchecked(&self, idx: I) -> &I::Output { + idx.get_unchecked(self) + } + + /// Slices the string mutably without checking bounds + /// + /// # Safety + /// Calling this method with an out-of-bounds index is undefined-behavior even if the resulting + /// reference is not used + #[must_use] + pub unsafe fn get_unchecked_mut(&mut self, idx: I) -> &mut I::Output { + idx.get_unchecked_mut(self) } } impl AsRef for AsciiStrArr { fn as_ref(&self) -> &AsciiStr { - self.as_ascii_str() + self.as_ascii() } } impl AsMut for AsciiStrArr { fn as_mut(&mut self) -> &mut AsciiStr { - self.as_ascii_str_mut() + self.as_ascii_mut() + } +} + +impl AsRef<[u8]> for AsciiStrArr { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl AsRef for AsciiStrArr { + fn as_ref(&self) -> &str { + self.as_str() } } impl PartialEq for AsciiStrArr { fn eq(&self, other: &Self) -> bool { - AsciiStr::eq(self.as_ascii_str(), other.as_ascii_str()) + AsciiStr::eq(self.as_ascii(), other.as_ascii()) } } impl PartialOrd for AsciiStrArr { fn partial_cmp(&self, other: &Self) -> Option { - AsciiStr::partial_cmp(self.as_ascii_str(), other.as_ascii_str()) + AsciiStr::partial_cmp(self.as_ascii(), other.as_ascii()) } } impl Ord for AsciiStrArr { fn cmp(&self, other: &Self) -> Ordering { - AsciiStr::cmp(self.as_ascii_str(), other.as_ascii_str()) + AsciiStr::cmp(self.as_ascii(), other.as_ascii()) } } impl Hash for AsciiStrArr { fn hash(&self, state: &mut H) { - AsciiStr::hash(self.as_ascii_str(), state) + AsciiStr::hash(self.as_ascii(), state) } } impl fmt::Debug for AsciiStrArr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - AsciiStr::fmt(self.as_ascii_str(), f) + AsciiStr::fmt(self.as_ascii(), f) } } impl fmt::Display for AsciiStrArr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - AsciiStr::fmt(self.as_ascii_str(), f) + AsciiStr::fmt(self.as_ascii(), f) } } @@ -130,16 +213,14 @@ impl serde::Serialize for AsciiStrArr { where S: serde::Serializer, { - serializer.serialize_str(self.as_ascii_str().as_str()) + serializer.serialize_str(self.as_ascii().as_str()) } } // TODO: Generalize this to `impl From<&[AsciiChar; M]> for AsciiStrArr where M <= N` impl From<&[AsciiChar; N]> for AsciiStrArr { fn from(src: &[AsciiChar; N]) -> Self { - let mut chars = [AsciiChar::Null; N]; - chars.copy_from_slice(src); - Self { chars, len: N } + Self::from(*src) } } @@ -167,35 +248,34 @@ impl TryFrom<&[u8; N]> for AsciiStrArr { impl TryFrom<&AsciiStr> for AsciiStrArr { type Error = TooLongError; - fn try_from(ascii_str: &AsciiStr) -> Result { - // Note: No space for null, this isn't null terminated - let len = ascii_str.len(); - if len > N { - return Err(TooLongError::); - } - - let mut chars = [AsciiChar::Null; N]; - chars[0..len].copy_from_slice(ascii_str.as_ref()); - Ok(Self { chars, len }) + fn try_from(ascii: &AsciiStr) -> Result { + Self::from_ascii(ascii) } } impl TryFrom<&[u8]> for AsciiStrArr { - type Error = FromByteStringError; + type Error = FromBytesError; - fn try_from(byte_str: &[u8]) -> Result { - let ascii_str = AsciiStr::from_ascii(byte_str).map_err(FromByteStringError::NotAscii)?; - - Self::try_from(ascii_str).map_err(FromByteStringError::TooLong) + fn try_from(bytes: &[u8]) -> Result { + Self::from_bytes(bytes) } } impl TryFrom<&str> for AsciiStrArr { - type Error = FromByteStringError; + type Error = FromBytesError; fn try_from(string: &str) -> Result { - let ascii_str = AsciiStr::from_ascii(string).map_err(FromByteStringError::NotAscii)?; + let ascii_str = AsciiStr::from_ascii(string).map_err(FromBytesError::NotAscii)?; - Self::try_from(ascii_str).map_err(FromByteStringError::TooLong) + Self::try_from(ascii_str).map_err(FromBytesError::TooLong) + } +} + +impl std::str::FromStr for AsciiStrArr { + type Err = FromBytesError; + + fn from_str(s: &str) -> Result { + // Simply delegate to [`Self::from_bytes`] + Self::from_bytes(s.as_bytes()) } } diff --git a/src/ascii_str_arr/error.rs b/src/ascii_str_arr/error.rs index 5797f0c..a4f66d9 100644 --- a/src/ascii_str_arr/error.rs +++ b/src/ascii_str_arr/error.rs @@ -13,9 +13,9 @@ pub struct NotAsciiError { pub pos: usize, } -/// Error returned from [`TryFrom<&[u8]> for AsciiStrArr`] +/// Error returned when converting a `&[u8]` to a `AsciiStrArr` #[derive(Debug, thiserror::Error)] -pub enum FromByteStringError { +pub enum FromBytesError { /// Too long #[error("String was too long")] TooLong(TooLongError), diff --git a/src/ascii_str_arr/slice.rs b/src/ascii_str_arr/slice.rs index 4bd0d13..236a3e1 100644 --- a/src/ascii_str_arr/slice.rs +++ b/src/ascii_str_arr/slice.rs @@ -7,40 +7,59 @@ use std::ops::{Index, IndexMut}; /// Helper trait for slicing a [`AsciiStrArr`] // Note: Adapted from `std::slice::SliceIndex` -pub trait SliceIndex -where - I: ?Sized, -{ +pub trait SliceIndex { /// Output type for this slicing type Output: ?Sized; - /// Tries to get the `idx`th element - fn get(&self, idx: I) -> Option<&Self::Output>; + /// Slices the string, if in bounds + fn get(self, ascii_str: &AsciiStrArr) -> Option<&Self::Output>; - /// Tries to get the `idx`th element mutably - fn get_mut(&mut self, idx: I) -> Option<&mut Self::Output>; + /// Slices the string mutably, if in bounds + fn get_mut(self, ascii_str: &mut AsciiStrArr) -> Option<&mut Self::Output>; + + /// Slices the string without checking bounds + /// + /// # Safety + /// Calling this method with an out-of-bounds index is undefined-behavior even if the resulting + /// reference is not used + unsafe fn get_unchecked(self, ascii_str: &AsciiStrArr) -> &Self::Output; + + /// Slices the string mutably without checking bounds + /// + /// # Safety + /// Calling this method with an out-of-bounds index is undefined-behavior even if the resulting + /// reference is not used + unsafe fn get_unchecked_mut(self, ascii_str: &mut AsciiStrArr) -> &mut Self::Output; } -impl SliceIndex for AsciiStrArr +impl SliceIndex for I where I: std::slice::SliceIndex<[AsciiChar]>, { - type Output = >::Output; + type Output = >::Output; - fn get(&self, idx: I) -> Option<&Self::Output> { - self.as_ascii_str().as_slice().get(idx) + fn get(self, ascii_str: &AsciiStrArr) -> Option<&Self::Output> { + ascii_str.as_ascii().as_slice().get(self) } - fn get_mut(&mut self, idx: I) -> Option<&mut Self::Output> { - self.as_ascii_str_mut().as_mut_slice().get_mut(idx) + fn get_mut(self, ascii_str: &mut AsciiStrArr) -> Option<&mut Self::Output> { + ascii_str.as_ascii_mut().as_mut_slice().get_mut(self) + } + + unsafe fn get_unchecked(self, ascii_str: &AsciiStrArr) -> &Self::Output { + ascii_str.as_ascii().as_slice().get_unchecked(self) + } + + unsafe fn get_unchecked_mut(self, ascii_str: &mut AsciiStrArr) -> &mut Self::Output { + ascii_str.as_ascii_mut().as_mut_slice().get_unchecked_mut(self) } } impl Index for AsciiStrArr where - Self: SliceIndex, + I: SliceIndex, { - type Output = >::Output; + type Output = ::Output; fn index(&self, idx: I) -> &Self::Output { self.get(idx).expect("Invalid index access") @@ -49,7 +68,7 @@ where impl IndexMut for AsciiStrArr where - Self: SliceIndex, + I: SliceIndex, { fn index_mut(&mut self, idx: I) -> &mut Self::Output { self.get_mut(idx).expect("Invalid index access")