Improved AsciiStrArr.

This commit is contained in:
2020-09-18 04:42:26 +01:00
parent d66b855ab3
commit fac333c0ed
4 changed files with 164 additions and 64 deletions

View File

@@ -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<const N: usize> {
chars: [AsciiChar; N],
/// Size
///
/// Invariant: Must be `< N`
// Invariant `self.len <= N`
len: usize,
}
// Length interface
impl<const N: usize> AsciiStrArr<N> {
/// 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<const N: usize> AsciiStrArr<N> {
/// 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<const N: usize> AsciiStrArr<N> {
/// Creates a string from a `&AsciiStr`
pub fn from_ascii(ascii: &AsciiStr) -> Result<Self, TooLongError<N>> {
// If we can't fit it, return Err
let len = ascii.len();
if len > N {
return Err(TooLongError::<N>);
}
// 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<Self, FromBytesError<N>> {
// 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<const N: usize> AsciiStrArr<N> {
/// Slices this string, if in bounds
#[must_use]
pub fn get<I: SliceIndex>(&self, idx: I) -> Option<&I::Output> {
idx.get(self)
}
/// Slices this string mutably, if in bounds
#[must_use]
pub fn get_mut<I: SliceIndex>(&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<I: SliceIndex>(&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<I: SliceIndex>(&mut self, idx: I) -> &mut I::Output {
idx.get_unchecked_mut(self)
}
}
impl<const N: usize> AsRef<AsciiStr> for AsciiStrArr<N> {
fn as_ref(&self) -> &AsciiStr {
self.as_ascii_str()
self.as_ascii()
}
}
impl<const N: usize> AsMut<AsciiStr> for AsciiStrArr<N> {
fn as_mut(&mut self) -> &mut AsciiStr {
self.as_ascii_str_mut()
self.as_ascii_mut()
}
}
impl<const N: usize> AsRef<[u8]> for AsciiStrArr<N> {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<const N: usize> AsRef<str> for AsciiStrArr<N> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<const N: usize> PartialEq for AsciiStrArr<N> {
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<const N: usize> PartialOrd for AsciiStrArr<N> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
AsciiStr::partial_cmp(self.as_ascii_str(), other.as_ascii_str())
AsciiStr::partial_cmp(self.as_ascii(), other.as_ascii())
}
}
impl<const N: usize> Ord for AsciiStrArr<N> {
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<const N: usize> Hash for AsciiStrArr<N> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
AsciiStr::hash(self.as_ascii_str(), state)
AsciiStr::hash(self.as_ascii(), state)
}
}
impl<const N: usize> fmt::Debug for AsciiStrArr<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
AsciiStr::fmt(self.as_ascii_str(), f)
AsciiStr::fmt(self.as_ascii(), f)
}
}
impl<const N: usize> fmt::Display for AsciiStrArr<N> {
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<const N: usize> serde::Serialize for AsciiStrArr<N> {
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<const N: usize, const M: usize> From<&[AsciiChar; M]> for AsciiStrArr<N> where M <= N`
impl<const N: usize> From<&[AsciiChar; N]> for AsciiStrArr<N> {
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<const N: usize> TryFrom<&[u8; N]> for AsciiStrArr<N> {
impl<const N: usize> TryFrom<&AsciiStr> for AsciiStrArr<N> {
type Error = TooLongError<N>;
fn try_from(ascii_str: &AsciiStr) -> Result<Self, Self::Error> {
// Note: No space for null, this isn't null terminated
let len = ascii_str.len();
if len > N {
return Err(TooLongError::<N>);
}
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, Self::Error> {
Self::from_ascii(ascii)
}
}
impl<const N: usize> TryFrom<&[u8]> for AsciiStrArr<N> {
type Error = FromByteStringError<N>;
type Error = FromBytesError<N>;
fn try_from(byte_str: &[u8]) -> Result<Self, Self::Error> {
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, Self::Error> {
Self::from_bytes(bytes)
}
}
impl<const N: usize> TryFrom<&str> for AsciiStrArr<N> {
type Error = FromByteStringError<N>;
type Error = FromBytesError<N>;
fn try_from(string: &str) -> Result<Self, Self::Error> {
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<const N: usize> std::str::FromStr for AsciiStrArr<N> {
type Err = FromBytesError<N>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// Simply delegate to [`Self::from_bytes`]
Self::from_bytes(s.as_bytes())
}
}

View File

@@ -13,9 +13,9 @@ pub struct NotAsciiError {
pub pos: usize,
}
/// Error returned from [`TryFrom<&[u8]> for AsciiStrArr<N>`]
/// Error returned when converting a `&[u8]` to a `AsciiStrArr`
#[derive(Debug, thiserror::Error)]
pub enum FromByteStringError<const LEN: usize> {
pub enum FromBytesError<const LEN: usize> {
/// Too long
#[error("String was too long")]
TooLong(TooLongError<LEN>),

View File

@@ -7,40 +7,59 @@ use std::ops::{Index, IndexMut};
/// Helper trait for slicing a [`AsciiStrArr`]
// Note: Adapted from `std::slice::SliceIndex`
pub trait SliceIndex<I>
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<const N: usize>(self, ascii_str: &AsciiStrArr<N>) -> 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<const N: usize>(self, ascii_str: &mut AsciiStrArr<N>) -> 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<const N: usize>(self, ascii_str: &AsciiStrArr<N>) -> &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<const N: usize>(self, ascii_str: &mut AsciiStrArr<N>) -> &mut Self::Output;
}
impl<I, const N: usize> SliceIndex<I> for AsciiStrArr<N>
impl<I> SliceIndex for I
where
I: std::slice::SliceIndex<[AsciiChar]>,
{
type Output = <I as std::slice::SliceIndex<[AsciiChar]>>::Output;
type Output = <Self as std::slice::SliceIndex<[AsciiChar]>>::Output;
fn get(&self, idx: I) -> Option<&Self::Output> {
self.as_ascii_str().as_slice().get(idx)
fn get<const N: usize>(self, ascii_str: &AsciiStrArr<N>) -> 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<const N: usize>(self, ascii_str: &mut AsciiStrArr<N>) -> Option<&mut Self::Output> {
ascii_str.as_ascii_mut().as_mut_slice().get_mut(self)
}
unsafe fn get_unchecked<const N: usize>(self, ascii_str: &AsciiStrArr<N>) -> &Self::Output {
ascii_str.as_ascii().as_slice().get_unchecked(self)
}
unsafe fn get_unchecked_mut<const N: usize>(self, ascii_str: &mut AsciiStrArr<N>) -> &mut Self::Output {
ascii_str.as_ascii_mut().as_mut_slice().get_unchecked_mut(self)
}
}
impl<I, const N: usize> Index<I> for AsciiStrArr<N>
where
Self: SliceIndex<I>,
I: SliceIndex,
{
type Output = <Self as SliceIndex<I>>::Output;
type Output = <I as SliceIndex>::Output;
fn index(&self, idx: I) -> &Self::Output {
self.get(idx).expect("Invalid index access")
@@ -49,7 +68,7 @@ where
impl<I, const N: usize> IndexMut<I> for AsciiStrArr<N>
where
Self: SliceIndex<I>,
I: SliceIndex,
{
fn index_mut(&mut self, idx: I) -> &mut Self::Output {
self.get_mut(idx).expect("Invalid index access")