diff --git a/Cargo.toml b/Cargo.toml index 7d910f5..16078d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nebz" -version = "0.1.2" +version = "0.2.0" edition = "2024" description = "an immutable non-empty bytes container" readme = "README.md" diff --git a/src/lib.rs b/src/lib.rs index ef721ae..19c272a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,8 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] +use core::num::NonZeroUsize; + /// A container to `T` that is known to be non-empty. /// /// A `T: AsRef<[u8]>` is empty if `AsRef::<[u8]>::as_ref(&T).is_empty()` is `true`. @@ -9,9 +11,9 @@ pub struct NonEmptyBz(T); impl NonEmptyBz { - /// Creates a [`NonEmptyBz`] if the given `bz` is not empty. + /// Creates a `NonEmptyBz` if the given `bz` is not empty. /// - /// If 'bz' is `[u8; N]` or `&[u8; N]`, consider using `NonEmptyBz::from_#_array()` constructors. + /// If `bz` is `[u8; N]` or `&[u8; N]`, consider using `NonEmptyBz::from_#_array()` constructor. pub fn new(bz: T) -> Option where T: AsRef<[u8]>, @@ -91,7 +93,7 @@ impl NonEmptyBz { NonEmptyBz(&self.0) } - /// Returns [`NonEmptyBz`] containing the [`AsRef::<[u8]>::as_ref`] of the inner value. + /// Returns [`NonEmptyBz`] containing the `AsRef::<[u8]>::as_ref` of the inner value. pub fn as_ref_slice(&self) -> NonEmptyBz<&[u8]> where T: AsRef<[u8]>, @@ -99,23 +101,19 @@ impl NonEmptyBz { NonEmptyBz(self.0.as_ref()) } - /// Returns the `len` of the inner value's [`AsRef::<[u8]>::as_ref`]. - pub fn len(&self) -> usize - where - T: AsRef<[u8]>, - { - self.0.as_ref().len() - } - - /// Returns true if the `len` of the inner value's [`AsRef::<[u8]>::as_ref`] is 0. - pub fn is_empty(&self) -> bool + /// Returns the `len` of the inner value's `AsRef::<[u8]>::as_ref`. + pub fn len(&self) -> NonZeroUsize where T: AsRef<[u8]>, { - self.0.as_ref().is_empty() + // SAFETY: `NonEmptyBz` guarantees that the contained value is non-empty, + // i.e. `self.0.as_ref().len() >= 1`. This invariant is maintained by: + // - The `NonEmptyBz::new()` constructor which rejects empty values + // - The array constructors which use compile-time assertions to ensure non-empty array + unsafe { NonZeroUsize::new_unchecked(self.0.as_ref().len()) } } - /// Unwraps the value, consuming the [`NonEmptyBz`]. + /// Unwraps the value, consuming the `NonEmptyBz`. pub fn into_inner(self) -> T { self.0 } @@ -132,9 +130,9 @@ impl NonEmptyBz<&T> { } impl NonEmptyBz<[u8; N]> { - /// Creates a [`NonEmptyBz`] containing the array `bz`. + /// Creates a `NonEmptyBz<[u8; N]>` containing the array `bz`. /// - /// Will cause compile-time error if `N` is 0. + /// Will cause compile-time error if `N == 0`. pub const fn from_owned_array(bz: [u8; N]) -> Self { const { assert!(N != 0) } Self(bz) @@ -142,9 +140,9 @@ impl NonEmptyBz<[u8; N]> { } impl<'a, const N: usize> NonEmptyBz<&'a [u8; N]> { - /// Creates a [`NonEmptyBz`] containing the borrowed array `bz`. + /// Creates a `NonEmptyBz<&[u8; N]>` containing the borrowed array `bz`. /// - /// Will cause compile-time error if `N` is 0. + /// Will cause compile-time error if `N == 0`. pub const fn from_borrowed_array(bz: &'a [u8; N]) -> Self { const { assert!(N != 0) } Self(bz) @@ -153,7 +151,8 @@ impl<'a, const N: usize> NonEmptyBz<&'a [u8; N]> { #[cfg(feature = "bytes")] impl From> for NonEmptyBz { - /// Creates a [`NonEmptyBz`] containing `Bytes` from a [`NonEmptyBz`] containing `&[u8]` by invoking [`Bytes::copy_from_slice`](bytes::Bytes::copy_from_slice). + /// Creates a `NonEmptyBz` from a `NonEmptyBz` containing `&[u8]` + /// by invoking [`Bytes::copy_from_slice`](bytes::Bytes::copy_from_slice). fn from(nebz_slice: NonEmptyBz<&[u8]>) -> Self { Self(bytes::Bytes::copy_from_slice(nebz_slice.get())) } @@ -220,4 +219,28 @@ mod tests { assert_eq!(last, expected_last); assert_eq!(rest, expected_split_last_rest); } + + #[rstest] + // single byte + #[case::single_byte_owned_array(NonEmptyBz::from_owned_array([42]), NonZeroUsize::MIN)] + #[case::single_byte_borrowed_array(NonEmptyBz::from_borrowed_array(b"a"), NonZeroUsize::MIN)] + #[case::single_byte_vec(NonEmptyBz::new(vec![0]).unwrap(), NonZeroUsize::MIN)] + #[case::single_byte_str(NonEmptyBz::new("z").unwrap(), NonZeroUsize::MIN)] + // multiple bytes + #[case::multiple_bytes_owned_array( + NonEmptyBz::from_owned_array([1, 2, 3, 4]), + 4.try_into().unwrap(), + )] + #[case::multiple_bytes_borrowed_array( + NonEmptyBz::from_borrowed_array(b"nebz"), + 4.try_into().unwrap(), + )] + #[case::multiple_bytes_vec(NonEmptyBz::new(vec![255, 0, 128]).unwrap(), 3.try_into().unwrap())] + #[case::multiple_bytes_str(NonEmptyBz::new("hello").unwrap(), 5.try_into().unwrap())] + fn len_works(#[case] nebz: NonEmptyBz, #[case] expected_len: NonZeroUsize) + where + T: AsRef<[u8]>, + { + assert_eq!(nebz.len(), expected_len); + } }