Merge rust-bitcoin/rust-bitcoin#2287: Implement custom `ArrayVec` that is `Copy`

7c3b198127 Add a simple `Copy`-able `ArrayVec` (Martin Habovstiak)
f17c0402b7 Add tools that help with making code `const` (Martin Habovstiak)

Pull request description:

  While we decided to use `arrayvec::ArrayVec`, unfortunately it currently isn't `Copy` even if `T` is because of unfortunate interactions with the `Drop` trait. So this adds a super-simple custom version that is `Copy` and may be extended if we need to or replaced with `arrayvec` if they start supporting `Copy`.

ACKs for top commit:
  tcharding:
    ACK 7c3b198127
  apoelstra:
    ACK 7c3b198127

Tree-SHA512: 545cb99c910f9455185a78a77a1110374e45a11e374c698316a222e599737b82ec2ac5c8ef1a8fe7037f20d938b1a4061685b6c673061b706dc9df9478de2b48
This commit is contained in:
Andrew Poelstra 2023-12-15 14:40:40 +00:00
commit 12e11a089c
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
3 changed files with 300 additions and 0 deletions

206
internals/src/array_vec.rs Normal file
View File

@ -0,0 +1,206 @@
// SPDX-License-Identifier: CC0-1.0
//! A simplified `Copy` version of `arrayvec::ArrayVec`.
use core::fmt;
pub use safety_boundary::ArrayVec;
/// Limits the scope of `unsafe` auditing.
// New trait impls and fns that don't need to access internals should go below the module, not
// inside it!
mod safety_boundary {
use core::mem::MaybeUninit;
use crate::const_tools::cond_const;
/// A growable contiguous collection backed by array.
#[derive(Copy)]
pub struct ArrayVec<T: Copy, const CAP: usize> {
len: usize,
data: [MaybeUninit<T>; CAP],
}
impl<T: Copy, const CAP: usize> ArrayVec<T, CAP> {
// The bounds are const-unstable until 1.61
cond_const! {
/// Creates an empty `ArrayVec`.
pub const(in rust_v_1_61 = "1.61") fn new() -> Self {
Self {
len: 0,
data: [MaybeUninit::uninit(); CAP],
}
}
/// Creates an `ArrayVec` initialized with the contets of `slice`.
///
/// # Panics
///
/// If the slice is longer than `CAP`.
pub const(in rust_v_1_61 = "1.61") fn from_slice(slice: &[T]) -> Self {
assert!(slice.len() <= CAP);
let mut data = [MaybeUninit::uninit(); CAP];
let mut i = 0;
// can't use mutable references and operators in const
while i < slice.len() {
data[i] = MaybeUninit::new(slice[i]);
i += 1;
}
Self {
len: slice.len(),
data,
}
}
}
// from_raw_parts is const-unstable until 1.64
cond_const! {
/// Returns a reference to the underlying data.
pub const(in rust_v_1_64 = "1.64") fn as_slice(&self) -> &[T] {
let ptr = &self.data as *const _ as *const T;
unsafe { core::slice::from_raw_parts(ptr, self.len) }
}
}
/// Returns a mutable reference to the underlying data.
pub fn as_mut_slice(&mut self) -> &mut [T] {
unsafe { &mut *(&mut self.data[..self.len] as *mut _ as *mut [T]) }
}
/// Adds an element into `self`.
///
/// # Panics
///
/// If the length would increase past CAP.
pub fn push(&mut self, element: T) {
assert!(self.len < CAP);
self.data[self.len] = MaybeUninit::new(element);
self.len += 1;
}
/// Copies and appends all elements from `slice` into `self`.
///
/// # Panics
///
/// If the length would increase past CAP.
pub fn extend_from_slice(&mut self, slice: &[T]) {
let new_len = self.len.checked_add(slice.len()).expect("integer/buffer overflow");
assert!(new_len <= CAP, "buffer overflow");
// SAFETY: MaybeUninit<T> has the same layout as T
let slice = unsafe { &*(slice as *const _ as *const [MaybeUninit<T>]) };
self.data[self.len..].copy_from_slice(slice);
self.len = new_len;
}
}
}
/// Clones the value *faster* than using `Copy`.
///
/// Because we avoid copying the uninitialized part of the array this copies the value faster than
/// memcpy.
#[allow(clippy::non_canonical_clone_impl)]
impl<T: Copy, const CAP: usize> Clone for ArrayVec<T, CAP> {
fn clone(&self) -> Self {
Self::from_slice(self)
}
}
impl<T: Copy, const CAP: usize> core::ops::Deref for ArrayVec<T, CAP> {
type Target = [T];
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
impl<T: Copy, const CAP: usize> core::ops::DerefMut for ArrayVec<T, CAP> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut_slice()
}
}
impl<T: Copy + Eq, const CAP: usize> Eq for ArrayVec<T, CAP> {}
impl<T: Copy + PartialEq, const CAP1: usize, const CAP2: usize> PartialEq<ArrayVec<T, CAP2>> for ArrayVec<T, CAP1> {
fn eq(&self, other: &ArrayVec<T, CAP2>) -> bool {
**self == **other
}
}
impl<T: Copy + PartialEq, const CAP: usize> PartialEq<[T]> for ArrayVec<T, CAP> {
fn eq(&self, other: &[T]) -> bool {
**self == *other
}
}
impl<T: Copy + PartialEq, const CAP: usize> PartialEq<ArrayVec<T, CAP>> for [T] {
fn eq(&self, other: &ArrayVec<T, CAP>) -> bool {
*self == **other
}
}
impl<T: Copy + PartialEq, const CAP: usize, const LEN: usize> PartialEq<[T; LEN]> for ArrayVec<T, CAP> {
fn eq(&self, other: &[T; LEN]) -> bool {
**self == *other
}
}
impl<T: Copy + PartialEq, const CAP: usize, const LEN: usize> PartialEq<ArrayVec<T, CAP>> for [T; LEN] {
fn eq(&self, other: &ArrayVec<T, CAP>) -> bool {
*self == **other
}
}
impl<T: Copy + Ord, const CAP: usize> Ord for ArrayVec<T, CAP> {
fn cmp(&self, other: &ArrayVec<T, CAP>) -> core::cmp::Ordering {
(**self).cmp(&**other)
}
}
impl<T: Copy + PartialOrd, const CAP1: usize, const CAP2: usize> PartialOrd<ArrayVec<T, CAP2>> for ArrayVec<T, CAP1> {
fn partial_cmp(&self, other: &ArrayVec<T, CAP2>) -> Option<core::cmp::Ordering> {
(**self).partial_cmp(&**other)
}
}
impl<T: Copy + fmt::Debug, const CAP: usize> fmt::Debug for ArrayVec<T, CAP> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
impl<T: Copy + core::hash::Hash, const CAP: usize> core::hash::Hash for ArrayVec<T, CAP> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
core::hash::Hash::hash(&**self, state)
}
}
#[cfg(test)]
mod tests {
use super::ArrayVec;
#[test]
fn arrayvec_ops() {
let mut av = ArrayVec::<_, 1>::new();
assert!(av.is_empty());
av.push(42);
assert_eq!(av.len(), 1);
assert_eq!(av, [42]);
}
#[test]
#[should_panic]
fn overflow_push() {
let mut av = ArrayVec::<_, 0>::new();
av.push(42);
}
#[test]
#[should_panic]
fn overflow_extend() {
let mut av = ArrayVec::<_, 0>::new();
av.extend_from_slice(&[42]);
}
}

View File

@ -0,0 +1,92 @@
//! Contains tools (workarounds) to make implementing `const fn`s easier.
/// Copies first `$len` bytes from `$slice` and returns them as an array.
///
/// Returns `None` if `$len > $slice.len()`. `$len` must be (obviously) statically known.
/// Calling from non-const context doesn't affect performance.
#[macro_export]
macro_rules! copy_byte_array_from_slice {
($slice:expr, $len:expr) => {
if $len > $slice.len() {
None
} else {
let mut array = [0u8; $len];
// Note: produces same assemble as copy_from_slice
let mut i = 0;
while i < $len {
array[i] = $slice[i];
i += 1;
}
Some(array)
}
};
}
pub use copy_byte_array_from_slice;
/// Concatenates two byte slices or byte arrays (or combination) to a single array.
///
/// # Panics
///
/// This macro panics if `$len` is not equal to the sum of `$a.len()` and `$b.len()`.
#[macro_export]
macro_rules! concat_bytes_to_arr {
($a:expr, $b:expr, $len:expr) => {{
// avoid repeated eval
let a = $a;
let b = $b;
#[allow(unconditional_panic)]
let _ = [(); 1][($len != a.len() + b.len()) as usize];
let mut output = [0u8; $len];
let mut i = 0;
while i < a.len() {
output[i] = $a[i];
i += 1;
}
while i < a.len() + b.len() {
output[i] = b[i - a.len()];
i += 1;
}
output
}};
}
pub use concat_bytes_to_arr;
#[macro_export]
/// Enables const fn in specified Rust version
macro_rules! cond_const {
($($(#[$attr:meta])* $vis:vis const(in $ver:ident $(= $human_ver:literal)?) fn $name:ident$(<$($gen:tt)*>)?($($args:tt)*) $(-> $ret:ty)? $body:block)+ ) => {
$(
#[cfg($ver)]
$(#[$attr])*
$(
#[doc = "\nNote: the function is only `const` in Rust "]
#[doc = $human_ver]
#[doc = "."]
)?
$vis const fn $name$(<$($gen)*>)?($($args)*) $(-> $ret)? $body
#[cfg(not($ver))]
$(#[$attr])*
$vis fn $name$(<$($gen)*>)?($($args)*) $(-> $ret)? $body
)+
};
($($(#[$attr:meta])* $vis:vis const(in $ver:ident $(= $human_ver:literal)?) unsafe fn $name:ident$(<$($gen:tt)*>)?($($args:tt)*) $(-> $ret:ty)? $body:block)+ ) => {
$(
#[cfg($ver)]
$(#[$attr])*
$(
#[doc = "\nNote: the function is only `const` in Rust "]
#[doc = $human_ver]
#[doc = " and newer."]
)?
$vis const unsafe fn $name$(<$($gen)*>)?($($args)*) $(-> $ret)? $body
#[cfg(not($ver))]
$(#[$attr])*
$vis unsafe fn $name$(<$($gen)*>)?($($args)*) $(-> $ret)? $body
)+
};
}
pub use cond_const;

View File

@ -20,6 +20,8 @@ extern crate alloc;
#[cfg(feature = "std")] #[cfg(feature = "std")]
extern crate std; extern crate std;
pub mod array_vec;
pub mod const_tools;
pub mod error; pub mod error;
pub mod macros; pub mod macros;
mod parse; mod parse;