From 3b4d814e5c8fda418c34a04c696c6dd08926568a Mon Sep 17 00:00:00 2001 From: Filipe Rodrigues Date: Thu, 8 Oct 2020 11:21:11 +0100 Subject: [PATCH] Removed `contracts` dependency. Added error alias `dcb::ascii_str_arr::FromUtf8Error`. Improved several functions within `dcb::ascii_str_arr`. Revised errors in `dcb::ascii_str_arr`. --- dcb/Cargo.toml | 1 - dcb/src/ascii_str_arr.rs | 72 +++++++++++++++++----------------- dcb/src/ascii_str_arr/error.rs | 26 +++++++----- dcb/src/lib.rs | 3 +- 4 files changed, 54 insertions(+), 48 deletions(-) diff --git a/dcb/Cargo.toml b/dcb/Cargo.toml index b2e1aa5..ea66029 100644 --- a/dcb/Cargo.toml +++ b/dcb/Cargo.toml @@ -16,7 +16,6 @@ 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/dcb/src/ascii_str_arr.rs b/dcb/src/ascii_str_arr.rs index c75612d..5fec6e9 100644 --- a/dcb/src/ascii_str_arr.rs +++ b/dcb/src/ascii_str_arr.rs @@ -8,7 +8,7 @@ mod test; mod visitor; // Exports -pub use error::{FromBytesError, NotAsciiError, TooLongError}; +pub use error::{FromBytesError, FromUtf8Error, NotAsciiError, TooLongError}; pub use slice::SliceIndex; // Imports @@ -48,8 +48,7 @@ impl AsciiStrArr { /// Returns the length of this string #[must_use] - #[contracts::debug_ensures(self.len <= N)] - pub fn len(&self) -> usize { + pub const fn len(&self) -> usize { self.len } @@ -64,14 +63,14 @@ impl AsciiStrArr { /// # Safety /// - All elements `0..new_len` must be initialized. /// - `new_len` must be less or equal to `N`. - #[contracts::debug_requires(new_len <= N)] - pub unsafe fn set_len(&mut self, new_len: usize) { + pub const unsafe fn set_len(&mut self, new_len: usize) { + debug_assert!(new_len <= N); self.len = new_len; } /// Returns if this string is empty #[must_use] - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.len() == 0 } } @@ -88,6 +87,7 @@ impl AsciiStrArr { // Then get a reference to them // SAFETY: The first `self.len` elements are initialized let chars = unsafe { MaybeUninit::slice_assume_init_ref(chars) }; + debug_assert!(chars.len() == self.len()); <&AsciiStr>::from(chars) } @@ -103,6 +103,7 @@ impl AsciiStrArr { // Then get a mutable reference to them // SAFETY: The first `self.len` elements are initialized let chars = unsafe { MaybeUninit::slice_assume_init_mut(chars) }; + debug_assert!(chars.len() == len); <&mut AsciiStr>::from(chars) } @@ -155,7 +156,7 @@ impl AsciiStrArr { /// /// # Safety /// All elements `0..self.len()` must remain initialized. - pub unsafe fn buffer_mut(&mut self) -> &mut [MaybeUninit; N] { + pub const unsafe fn buffer_mut(&mut self) -> &mut [MaybeUninit; N] { &mut self.chars } } @@ -167,27 +168,27 @@ impl AsciiStrArr { let ascii = ascii.as_ref(); // If it has too many elements, return Err - let len = ascii.len(); - if len > N { + if ascii.len() > N { return Err(TooLongError::); } // Else create an uninitialized array and copy over the initialized characters - let mut chars = MaybeUninit::uninit_array(); - for (idx, c) in chars.iter_mut().take(len).enumerate() { - // Write each character - // SAFETY: `idx` is within bounds (`0..len`) - // Note: `MaybeUnit::drop` is a no-op, so we can use normal assignment. - *c = MaybeUninit::new(*unsafe { ascii.get_unchecked(idx) }); + let mut chars: [MaybeUninit; N] = MaybeUninit::uninit_array(); + for (uninit, &ascii) in chars.iter_mut().zip(ascii) { + *uninit = MaybeUninit::new(ascii); } - Ok(Self { chars, len }) + // SAFETY: We initialized `ascii.len()` characters from `ascii`. + Ok(Self { chars, len: ascii.len() }) } - /// Creates a string from a `&[u8]` + /// Creates a string from bytes pub fn from_bytes>(bytes: &B) -> Result> { // Get the bytes as ascii first - let ascii = AsciiStr::from_ascii(bytes).map_err(FromBytesError::NotAscii)?; + let ascii = AsciiStr::from_ascii(bytes) + .map_err(ascii::AsAsciiStrError::valid_up_to) + .map_err(|pos| NotAsciiError { pos }) + .map_err(FromBytesError::NotAscii)?; // Then try to convert them Self::from_ascii(ascii).map_err(FromBytesError::TooLong) @@ -321,10 +322,7 @@ impl<'de, const N: usize> serde::Deserialize<'de> for AsciiStrArr { where D: serde::Deserializer<'de>, { - match deserializer.deserialize_str(DeserializerVisitor) { - Ok(string) => Ok(string), - Err(err) => Err(err), - } + deserializer.deserialize_str(DeserializerVisitor) } } @@ -333,14 +331,15 @@ impl serde::Serialize for AsciiStrArr { where S: serde::Serializer, { - serializer.serialize_str(self.as_ascii().as_str()) + // Serialize as an ascii string + serializer.serialize_str(self.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 { - Self::from(*src) + >::from(*src) } } @@ -348,6 +347,8 @@ impl From<&[AsciiChar; N]> for AsciiStrArr { impl From<[AsciiChar; N]> for AsciiStrArr { fn from(chars: [AsciiChar; N]) -> Self { let chars = chars.map(MaybeUninit::new); + + // SAFETY: All characters up to `N` are initialized. Self { chars, len: N } } } @@ -357,13 +358,15 @@ impl TryFrom<&[u8; N]> for AsciiStrArr { type Error = NotAsciiError; fn try_from(byte_str: &[u8; N]) -> Result { - let mut ascii_str = [AsciiChar::Null; N]; - #[allow(clippy::map_err_ignore)] // The error doesn't contain anything - for (pos, (&byte, ascii)) in byte_str.iter().zip(ascii_str.iter_mut()).enumerate() { - *ascii = AsciiChar::from_ascii(byte).map_err(|_| NotAsciiError { pos })?; + let mut chars = MaybeUninit::uninit_array(); + + for (pos, (&byte, ascii)) in byte_str.iter().zip(&mut chars).enumerate() { + *ascii = AsciiChar::from_ascii(byte).map(MaybeUninit::new).map_err(|_err| NotAsciiError { pos })?; } - Ok(Self::from(ascii_str)) + // SAFETY: We initialize `chars` from `byte_str`, which is + // initialized up to `len` bytes. + Ok(Self { chars, len: byte_str.len() }) } } @@ -384,20 +387,17 @@ impl TryFrom<&[u8]> for AsciiStrArr { } impl TryFrom<&str> for AsciiStrArr { - type Error = FromBytesError; + type Error = FromUtf8Error; - fn try_from(string: &str) -> Result { - let ascii_str = AsciiStr::from_ascii(string).map_err(FromBytesError::NotAscii)?; - - Self::try_from(ascii_str).map_err(FromBytesError::TooLong) + fn try_from(s: &str) -> Result { + Self::from_bytes(s.as_bytes()) } } impl std::str::FromStr for AsciiStrArr { - type Err = FromBytesError; + type Err = FromUtf8Error; fn from_str(s: &str) -> Result { - // Simply delegate to [`Self::from_bytes`] Self::from_bytes(s.as_bytes()) } } diff --git a/dcb/src/ascii_str_arr/error.rs b/dcb/src/ascii_str_arr/error.rs index a4f66d9..bc73c6f 100644 --- a/dcb/src/ascii_str_arr/error.rs +++ b/dcb/src/ascii_str_arr/error.rs @@ -1,20 +1,23 @@ //! Errors -/// Error returned when a string was too long to be converted -#[derive(Debug, thiserror::Error)] -#[error("String was too long (max is {} characters)", LEN)] +/// The given string was too long to be converted. +#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] +#[derive(thiserror::Error)] +#[error("String must be at most {} characters", LEN)] pub struct TooLongError; -/// Error returned when an input string contained non-ascii characters -#[derive(Debug, thiserror::Error)] -#[error("String contained non-ascii characters (first found at {pos})")] +/// The given string has non-ascii characters. +#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] +#[derive(thiserror::Error)] +#[error("Character at pos {pos} was not ascii")] pub struct NotAsciiError { - /// Index that contained the first non-ascii character + /// Index of the first non-ascii character pub pos: usize, } -/// Error returned when converting a `&[u8]` to a `AsciiStrArr` -#[derive(Debug, thiserror::Error)] +/// Error returned when converting a byte string to an [`AsciiStrArr`](super::AsciiStrArr). +#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] +#[derive(thiserror::Error)] pub enum FromBytesError { /// Too long #[error("String was too long")] @@ -22,5 +25,8 @@ pub enum FromBytesError { /// Not ascii #[error("String contained non-ascii characters")] - NotAscii(ascii::AsAsciiStrError), + NotAscii(NotAsciiError), } + +/// Error returned when converting a utf-8 [`String`] to an [`AsciiStrArr`](super::AsciiStrArr). +pub type FromUtf8Error = FromBytesError; diff --git a/dcb/src/lib.rs b/dcb/src/lib.rs index 871c3f8..ae2c007 100644 --- a/dcb/src/lib.rs +++ b/dcb/src/lib.rs @@ -44,7 +44,8 @@ unsafe_block_in_unsafe_fn, maybe_uninit_uninit_array, maybe_uninit_slice, - array_map + array_map, + const_mut_refs )] // Lints #![warn(clippy::restriction, clippy::pedantic, clippy::nursery)]