diff --git a/Cargo.toml b/Cargo.toml index 13917b0..43e1129 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,8 @@ harness = false [features] default = ["std"] std = [] +# For the const_helper feature MSRV 1.63.0 is required +const_helper = [] [profile.bench] debug = true diff --git a/src/array_string.rs b/src/array_string.rs index 227e01d..fcd3101 100644 --- a/src/array_string.rs +++ b/src/array_string.rs @@ -83,6 +83,18 @@ impl ArrayString ArrayString { xs: MakeMaybeUninit::ARRAY, len: 0 } } + + /// Create a new `ArrayString` from raw parts (const fn). + /// + /// # Safety + /// The caller must ensure that the provided `len` is valid, + /// i.e. that `len <= CAP` and that the first `len` bytes of `xs` form a valid UTF-8 string. + pub const unsafe fn from_raw_parts(xs: [MaybeUninit; CAP], len: usize) -> Self { + assert_capacity_limit_const!(CAP); + assert_capacity_len_const!(CAP, len); + ArrayString { xs, len: len as _ } + } + /// Return the length of the string. #[inline] pub const fn len(&self) -> usize { self.len as usize } diff --git a/src/arrayvec.rs b/src/arrayvec.rs index e5ea52d..1a867fc 100644 --- a/src/arrayvec.rs +++ b/src/arrayvec.rs @@ -101,6 +101,17 @@ impl ArrayVec { ArrayVec { xs: MakeMaybeUninit::ARRAY, len: 0 } } + /// Create an `ArrayVec` from raw parts. + /// + /// # Safety + /// The caller must ensure that the first `len` elements of `xs` are + /// properly initialized and that len is less than or equal to `CAP`. + pub const unsafe fn from_raw_parts(xs: [MaybeUninit; CAP], len: usize) -> Self { + assert_capacity_limit_const!(CAP); + assert_capacity_len_const!(CAP, len); + ArrayVec { xs, len: len as LenUint } + } + /// Return the number of elements in the `ArrayVec`. /// /// ``` @@ -637,7 +648,7 @@ impl ArrayVec { /// assert_eq!(&v1[..], &[3]); /// assert_eq!(&v2[..], &[1, 2]); /// ``` - pub fn drain(&mut self, range: R) -> Drain + pub fn drain(&mut self, range: R) -> Drain<'_, T, CAP> where R: RangeBounds { // Memory safety @@ -664,7 +675,7 @@ impl ArrayVec { self.drain_range(start, end) } - fn drain_range(&mut self, start: usize, end: usize) -> Drain + fn drain_range(&mut self, start: usize, end: usize) -> Drain<'_, T, CAP> { let len = self.len(); @@ -1126,7 +1137,7 @@ impl ArrayVec { let mut guard = ScopeExitGuard { value: &mut self.len, data: len, - f: move |&len, self_len| { + f: move |&len, self_len: &mut &mut LenUint| { **self_len = len as LenUint; } }; diff --git a/src/const_helper.rs b/src/const_helper.rs new file mode 100644 index 0000000..15430b5 --- /dev/null +++ b/src/const_helper.rs @@ -0,0 +1,149 @@ +use core::mem::MaybeUninit; + +use crate::ArrayString; + +/// Creates a const ArrayString from a str slice. +pub const fn str(s: &str) -> ArrayString { + assert_capacity_limit_const!(CAP); + + let bytes = s.as_bytes(); + let len = bytes.len(); + + // Check that capacity is not exceeded + if len > CAP { + panic!("ArrayString: capacity exceeded in const_str"); + } + + let mut xs = [MaybeUninit::::uninit(); CAP]; + let mut i = 0; + while i < len { + xs[i] = MaybeUninit::new(bytes[i]); + i += 1; + } + + // Safety: We have initialized `len` bytes in `xs` + // and ensured that `len <= CAP` before + // and s is a valid UTF-8 string. + unsafe { ArrayString::from_raw_parts(xs, len) } +} + +/// Create a const ArrayString from a byte slice. +pub const fn byte_str(bytes: &[u8]) -> ArrayString { + // for the const_helper feature MSRV 1.63.0 is required + #[allow(clippy::incompatible_msrv)] + let Ok(str) = core::str::from_utf8(bytes) else { + panic!("ArrayString: invalid UTF-8 in const_byte_str"); + }; + crate::const_helper::str(str) +} + +/// Creates a const ArrayString from a str slice. +/// +/// # Examples +/// ```rust +/// use arrayvec::array_str; +/// // With inferred capacity +/// const S: arrayvec::ArrayString<5> = array_str!("hello"); +/// assert_eq!(&S, "hello"); +/// // With specified capacity +/// const S2: arrayvec::ArrayString<10> = array_str!("hello", 10); +/// assert_eq!(&S2, "hello"); +/// assert_eq!(S2.capacity(), 10); +/// ``` +#[macro_export] +macro_rules! array_str { + ($str:expr) => { + $crate::const_helper::str::<{ $str.len() }>($str) + }; + ($str:expr, $cap:expr) => { + $crate::const_helper::str::<$cap>($str) + }; +} + +/// Creates a const ArrayString from a byte slice. +/// +/// # Examples +/// ```rust +/// use arrayvec::array_bstr; +/// // With inferred capacity +/// const B: arrayvec::ArrayString<5> = array_bstr!(b"hello"); +/// assert_eq!(&B, "hello"); +/// // With specified capacity +/// const B2: arrayvec::ArrayString<10> = array_bstr!(b"hello", 10); +/// assert_eq!(&B2, "hello"); +/// assert_eq!(B2.capacity(), 10); +/// ``` +#[macro_export] +macro_rules! array_bstr { + ($bstr:expr) => { + $crate::const_helper::byte_str::<{ $bstr.len() }>($bstr) + }; + ($bstr:expr, $cap:expr) => { + $crate::const_helper::byte_str::<$cap>($bstr) + }; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn array_str() { + const S_EMPTY: ArrayString<0> = array_str!(""); + assert_eq!(&S_EMPTY, ""); + + const S_EMPTY_CAP5: ArrayString<5> = array_str!("", 5); + assert_eq!(&S_EMPTY_CAP5, ""); + + const S1: ArrayString<5> = array_str!("hello"); + assert_eq!(&S1, "hello"); + + const S2: ArrayString<10> = array_str!("hello", 10); + assert_eq!(&S2, "hello"); + } + + #[test] + fn array_bstr() { + const B_EMPTY: ArrayString<0> = array_bstr!(b""); + assert_eq!(&B_EMPTY, ""); + + const B_EMPTY_CAP5: ArrayString<5> = array_bstr!(b"", 5); + assert_eq!(&B_EMPTY_CAP5, ""); + + const B1: ArrayString<5> = array_bstr!(b"hello"); + assert_eq!(&B1, "hello"); + + const B2: ArrayString<10> = array_bstr!(b"hello", 10); + assert_eq!(&B2, "hello"); + } + + #[test] + #[should_panic] + fn fail_empty() { + let _bad_empty = array_str!("hello", 0); + } + + #[test] + #[should_panic] + fn fail_bempty() { + let _bad_bempty = array_bstr!(b"hello", 0); + } + + #[test] + #[should_panic] + fn fail_cap() { + let _bad_small = array_str!("hello", 4); + } + + #[test] + #[should_panic] + fn fail_bcap() { + let _bad_bsmall = array_bstr!(b"hello", 4); + } + + #[test] + #[should_panic] + fn fail_utf8() { + let _bad_utf8 = array_bstr!(b"\xFF\xFF\xFF", 4); + } +} diff --git a/src/lib.rs b/src/lib.rs index 5c4bcee..807bf48 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,8 +36,8 @@ pub(crate) type LenUint = u16; macro_rules! assert_capacity_limit { ($cap:expr) => { - if std::mem::size_of::() > std::mem::size_of::() { - if $cap > LenUint::MAX as usize { + if std::mem::size_of::() > std::mem::size_of::<$crate::LenUint>() { + if $cap > $crate::LenUint::MAX as usize { #[cfg(not(target_pointer_width = "16"))] panic!("ArrayVec: largest supported capacity is u32::MAX"); #[cfg(target_pointer_width = "16")] @@ -49,20 +49,30 @@ macro_rules! assert_capacity_limit { macro_rules! assert_capacity_limit_const { ($cap:expr) => { - if std::mem::size_of::() > std::mem::size_of::() { - if $cap > LenUint::MAX as usize { + if std::mem::size_of::() > std::mem::size_of::<$crate::LenUint>() { + if $cap > $crate::LenUint::MAX as usize { [/*ArrayVec: largest supported capacity is u32::MAX*/][$cap] } } } } +macro_rules! assert_capacity_len_const { + ($cap:expr, $len:expr) => { + if $len > $cap { + [/*ArrayVec/ArrayString: len over capacity*/][$len] + } + }; +} + mod arrayvec_impl; mod arrayvec; mod array_string; mod char; mod errors; mod utils; +#[cfg(feature = "const_helper")] +pub mod const_helper; pub use crate::array_string::ArrayString; pub use crate::errors::CapacityError; diff --git a/tests/tests.rs b/tests/tests.rs index ff779ba..b20a7bf 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -4,6 +4,7 @@ extern crate arrayvec; use arrayvec::ArrayVec; use arrayvec::ArrayString; use std::mem; +use std::mem::MaybeUninit; use arrayvec::CapacityError; use std::collections::HashMap; @@ -774,3 +775,28 @@ fn test_arraystring_zero_filled_has_some_sanity_checks() { assert_eq!(string.as_str(), "\0\0\0\0"); assert_eq!(string.len(), 4); } + +#[test] +fn test_array_vec_from_parts() { + let mut xs = [MaybeUninit::::uninit(); 4]; + xs[0] = MaybeUninit::new(1); + xs[1] = MaybeUninit::new(2); + + // # Safety - we have initialized 2 elements in xs + let array = unsafe { ArrayVec::from_raw_parts(xs, 2) }; + assert_eq!(&array[..], &[1, 2]); + assert_eq!(array.len(), 2); +} + +#[test] +fn test_array_str_from_parts() { + let mut xs = [MaybeUninit::::uninit(); 4]; + xs[0] = MaybeUninit::new(b'a'); + xs[1] = MaybeUninit::new(b'b'); + xs[2] = MaybeUninit::new(b'c'); + + // # Safety - we have initialized 3 elements in xs + let array = unsafe { ArrayString::from_raw_parts(xs, 3) }; + assert_eq!(&array[..], "abc"); + assert_eq!(array.len(), 3); +} \ No newline at end of file