Skip to content

Vec improvements #427

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

- Added `format` macro.
- Added `String::from_utf16`.
- Added `Vec::spare_capacity_mut`
- Implemented `From<[T; N]>` for Vec
- Added `vec` macro for Vec

### Changed

Expand Down
5 changes: 5 additions & 0 deletions src/sealed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ pub(crate) const fn smaller_than<const N: usize, const MAX: usize>() {
Assert::<N, MAX>::LESS;
}

#[allow(dead_code, path_statements, clippy::no_effect)]
pub(crate) const fn greater_than_eq<const N: usize, const MIN: usize>() {
Assert::<N, MIN>::GREATER_EQ;
}

#[allow(dead_code, path_statements, clippy::no_effect)]
pub(crate) const fn greater_than_eq_0<const N: usize>() {
Assert::<N, 0>::GREATER_EQ;
Expand Down
136 changes: 135 additions & 1 deletion src/vec.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
use core::{cmp::Ordering, fmt, hash, iter::FromIterator, mem::MaybeUninit, ops, ptr, slice};
use core::{
cmp::Ordering,
fmt, hash,
iter::FromIterator,
mem::{ManuallyDrop, MaybeUninit},
ops, ptr, slice,
};

use crate::sealed;

/// A fixed capacity [`Vec`](https://doc.rust-lang.org/std/vec/struct.Vec.html).
///
Expand Down Expand Up @@ -821,6 +829,36 @@ impl<T, const N: usize> Vec<T, N> {
// All item are processed. This can be optimized to `set_len` by LLVM.
drop(g);
}

/// Returns the remaining spare capacity of the vector as a slice of `MaybeUninit<T>`.
///
/// The returned slice can be used to fill the vector with data before marking the data as
/// initialized using the `set_len` method.
///
/// # Examples
///
/// ```
/// use heapless::Vec;
///
/// // Allocate vector big enough for 10 elements.
/// let mut v: Vec<_, 10> = Vec::new();
///
/// // Fill in the first 3 elements.
/// let uninit = v.spare_capacity_mut();
/// uninit[0].write(0);
/// uninit[1].write(1);
/// uninit[2].write(2);
///
/// // Mark the first 3 elements of the vector as being initialized.
/// unsafe {
/// v.set_len(3);
/// }
///
/// assert_eq!(&v, &[0, 1, 2]);
/// ```
pub fn spare_capacity_mut(&mut self) -> &mut [MaybeUninit<T>] {
&mut self.buffer[self.len..]
}
}

// Trait implementations
Expand Down Expand Up @@ -858,6 +896,34 @@ impl<T, const N: usize> Drop for Vec<T, N> {
}
}

impl<T, const N: usize, const M: usize> From<[T; M]> for Vec<T, N> {
/// Constructs a vector of capacity `N` by copying in an array of size `M`, where `M` is less
/// or equal to `N`.
///
/// If `M` is greater than `N` or if `M` is 0, a compile-time error will be raised.
fn from(src: [T; M]) -> Self {
// Statically assert that N >= M and M > 0
sealed::greater_than_eq::<N, M>();
sealed::greater_than_0::<M>();

let mut vec = Self::new();
// Don't run drop code for src, since we're essentially moving it
let src = ManuallyDrop::new(src);
unsafe {
// Unsafe: Cast ManuallyDrop<[T; M]> to [MaybeUninit<T>; M], which have the same layout
let src_mu: [MaybeUninit<T>; M] = core::mem::transmute_copy(&src);
// Unsafe: Reinterpret vec.buffer ([MaybeUninit<T>; N]) as [MaybeUninit<T>; M], which
// is safe as long as N >= M, then write src into the buffer
vec.buffer
.as_mut_ptr()
.cast::<[MaybeUninit<T>; M]>()
.write(src_mu)
};
vec.len = M;
vec
}
}

impl<'a, T: Clone, const N: usize> TryFrom<&'a [T]> for Vec<T, N> {
type Error = ();

Expand Down Expand Up @@ -1175,6 +1241,39 @@ where
}
}

#[macro_export]
/// Shorthand for defining vectors using array syntax.
///
/// Create `Vec` with a list of elements and capacity:
/// ```
/// let v = heapless::vec![1, 2, 3; 7];
/// assert_eq!(v.as_slice(), &[1, 2, 3]);
/// assert_eq!(v.capacity(), 7);
/// ```
///
/// Create `Vec` with a repeated element, length, and capacity:
/// ```
/// let v = heapless::vec!['a'; 3; 4];
/// assert_eq!(v.as_slice(), &['a', 'a', 'a']);
/// assert_eq!(v.capacity(), 4);
/// ```
///
/// Unlike the `std` version of this macro, the repeat element must be `Copy` or a constant, like
/// with repeat elements in array expressions.
macro_rules! vec {
(; $cap:expr) => {
heapless::Vec::<_, $cap>::new()
};

($($elem:expr),+ ; $cap:expr) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be consistent with std.

As it's written now, vec![42; 5] in std it would create a vec with five 42's, while in heapless it creates a vec with a single 42 with a capacity of 5.

IMO the macro should behave like this:

  • vec![1,2,3]: 3 elements, capacity is inferred.
  • vec![42; 3]: element repeated 3 times, capacity is inferred.

and then invent some extra syntax for specifying capacity explicitly that doesn't conflict with that.

In #369 we had the same issue, we went with format!(256; "foo {}", 42) where 256 is the capacity, but that doesn't work well here...

heapless::Vec::<_, $cap>::from([$($elem),+])
};

($elem:expr ; $len:expr ; $cap:expr) => {
heapless::Vec::<_, $cap>::from([$elem; $len])
};
}

#[cfg(test)]
mod tests {
use crate::Vec;
Expand Down Expand Up @@ -1581,4 +1680,39 @@ mod tests {
// Validate full
assert!(v.is_full());
}

#[test]
fn spare_capacity_mut() {
let mut v: Vec<_, 4> = Vec::new();
let uninit = v.spare_capacity_mut();
assert_eq!(uninit.len(), 4);
uninit[0].write(1);
uninit[1].write(2);
uninit[2].write(3);
unsafe { v.set_len(3) };
assert_eq!(v.as_slice(), &[1, 2, 3]);

let uninit = v.spare_capacity_mut();
assert_eq!(uninit.len(), 1);
uninit[0].write(4);
unsafe { v.set_len(4) };
assert_eq!(v.as_slice(), &[1, 2, 3, 4]);

assert!(v.spare_capacity_mut().is_empty());
}

#[test]
fn from() {
let v: Vec<_, 4> = Vec::from([1, 2]);
assert_eq!(v.as_slice(), &[1, 2]);
let v: Vec<_, 4> = Vec::from([1, 2, 3, 4]);
assert_eq!(v.as_slice(), &[1, 2, 3, 4]);
}
}

/// ```compile_fail
/// use heapless::Vec;
///
/// let v: Vec<_, 4> = Vec::from([1, 2, 3, 4, 5]);
/// ```
fn _doc_test() {}