Skip to content

Add extend_from_array #79892

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 4 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
46 changes: 46 additions & 0 deletions library/alloc/src/collections/vec_deque/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2737,6 +2737,52 @@ impl<A> Extend<A> for VecDeque<A> {
self.push_back(elem);
}

#[inline]
fn extend_from_array<const N: usize>(&mut self, array: [A; N]) {
self.reserve(N);

// Pre-leak the items in the array, since we'll duplicate them below.
// They'll never *actually* leak since nothing in the rest of the method can fail.
let array = ManuallyDrop::new(array);

let available_at_head = self.cap() - self.head;
if available_at_head >= N {
// The easy case; we can copy it all at once.

// SAFETY: We just checked we have enough space without overrunning capacity.
// Thus the additions are in-bounds and cannot wrap.
// The copy is non-overlapping since the input array is owned.
// Duplicating the items is fine as we've already kept them from being dropped.
unsafe {
let end = self.ptr().add(self.head);
ptr::copy_nonoverlapping(array.as_ptr(), end, N);
}
} else {
// The harder case; we need to copy some of the items to the end and some to the beginning.

// SAFETY: We're copying exactly the number to go up to the capacity but not over.
// Thus the additions are in-bounds and cannot wrap.
// The copy is non-overlapping since the input array is owned.
// Duplicating the items is fine as we've already kept them from being dropped.
unsafe {
let end = self.ptr().add(self.head);
ptr::copy_nonoverlapping(array.as_ptr(), end, available_at_head);
}

// SAFETY: The reserve ensured that we have space for N, and we're copying less than that.
// Thus the additions are in-bounds and cannot wrap.
// The copy is non-overlapping since the input array is owned.
// Duplicating the items is fine as we've already kept them from being dropped.
unsafe {
let remaining_to_copy = N - available_at_head;
let array_ptr = array.as_ptr().add(available_at_head);
ptr::copy_nonoverlapping(array_ptr, self.ptr(), remaining_to_copy);
}
}

self.head = self.wrap_add(self.head, N);
}

#[inline]
fn extend_reserve(&mut self, additional: usize) {
self.reserve(additional);
Expand Down
1 change: 1 addition & 0 deletions library/alloc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
#![feature(dropck_eyepatch)]
#![feature(exact_size_is_empty)]
#![feature(exclusive_range_pattern)]
#![feature(extend_from_array)]
#![feature(extend_one)]
#![feature(fmt_internals)]
#![feature(fn_traits)]
Expand Down
39 changes: 39 additions & 0 deletions library/alloc/src/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2256,6 +2256,45 @@ impl<T, A: Allocator> Extend<T> for Vec<T, A> {
self.push(item);
}

/// Extends this vector by moving the elements of the array to the end of the vector.
///
/// You can think of a call to
/// ```rust
/// #![feature(extend_from_array)]
/// # let mut v = vec![];
/// v.extend_from_array([7, 13, 42]);
/// ```
/// as though it were
/// ```rust
/// # let mut v = vec![];
/// v.reserve(3);
/// v.push(7);
/// v.push(13);
/// v.push(42);
/// ```
/// just simpler to type and easier on the optimizer.
///
/// You should continue to use [`push`](Self::push) if you only have one element;
/// There's no advantage to doing `.extend_from_array([x])` instead.
#[inline]
fn extend_from_array<const N: usize>(&mut self, array: [T; N]) {
self.reserve(N);

// Pre-leak the items in the array, since we'll duplicate them below.
// They'll never *actually* leak since nothing in the rest of the method can fail.
let array = ManuallyDrop::new(array);

// SAFETY: The reserve has guaranteed that we have enough space available.
// Thus the additions are in-bounds and cannot wrap.
// The copy is non-overlapping since the input array is owned.
// Duplicating the items is fine as we've already kept them from being dropped.
unsafe {
let end = self.as_mut_ptr().add(self.len);
Comment on lines +2291 to +2292
Copy link
Member

Choose a reason for hiding this comment

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

There's a safe way to get the end pointer: self.as_mut_ptr_range().end

ptr::copy_nonoverlapping(array.as_ptr(), end, N);
self.len += N;
}
}

#[inline]
fn extend_reserve(&mut self, additional: usize) {
self.reserve(additional);
Expand Down
1 change: 1 addition & 0 deletions library/alloc/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#![feature(const_cow_is_borrowed)]
#![feature(drain_filter)]
#![feature(exact_size_is_empty)]
#![feature(extend_from_array)]
#![feature(new_uninit)]
#![feature(pattern)]
#![feature(str_split_once)]
Expand Down
14 changes: 14 additions & 0 deletions library/alloc/tests/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1954,3 +1954,17 @@ fn test_vec_swap() {
assert_eq!(a[0], 42);
assert_eq!(n, 0);
}

#[test]
fn test_vec_extend_from_array() {
let mut v = Vec::from("Hello");
v.extend_from_array(*b" ");
v.extend_from_array(*b"World");
v.push(b'!');
assert_eq!(v, b"Hello World!");

// And use a Drop type to check for obvious use-after-free or similar
let mut v = vec![String::from("Hello")];
v.extend_from_array([String::from(" "), String::from("world")]);
assert_eq!(v.concat(), "Hello world");
}
31 changes: 31 additions & 0 deletions library/alloc/tests/vec_deque.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1728,3 +1728,34 @@ fn test_zero_sized_push() {
}
}
}

#[test]
fn test_vecdeque_extend_from_array() {
// Easy case where it's just adding to the end
let mut v = VecDeque::with_capacity(15);
v.extend_from_array(*b"12345");
v.extend_from_array(*b"67890");
v.extend_from_array(*b"abcde");
assert_eq!(v, b"1234567890abcde");

// Check that the head hitting the end wraps it back to the correct place
let mut v = VecDeque::with_capacity(7);
v.extend_from_array(*b"1234");
v.pop_front();
v.extend_from_array(*b"5678");
assert_eq!(v.as_slices(), (&b"2345678"[..], &b""[..]));
v.pop_front();
v.push_back(b'!');
assert_eq!(v.as_slices(), (&b"345678"[..], &b"!"[..]));

// The version that needs two copies because it wraps around
let mut v = VecDeque::with_capacity(15);
v.extend_from_array(*b"1234567890");
for _ in 0..5 {
v.pop_front().unwrap();
}
v.extend_from_array(*b"ABCDEFGHIJ");
assert_eq!(v, b"67890ABCDEFGHIJ");
assert_eq!(v.capacity(), 15);
assert_eq!(v.as_slices(), (&b"67890ABCDEF"[..], &b"GHIJ"[..]));
}
34 changes: 34 additions & 0 deletions library/core/src/iter/traits/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,40 @@ pub trait Extend<A> {
self.extend(Some(item));
}

/// Extends a collection by moving the elements from the array.
///
/// You should call this if you have multiple elements to add and have them all already,
/// as it allows the container to optimize that where possible.
///
/// # Examples
///
/// ```rust
/// #![feature(extend_from_array)]
/// let mut v = vec![0; 3];
/// v.extend_from_array([1, 1, 2, 3, 5]);
/// assert_eq!(v, [0, 0, 0, 1, 1, 2, 3, 5]);
/// ```
///
/// Extending from an empty array is allowed, but of course doesn't affect the collection:
/// ```rust
/// #![feature(extend_from_array)]
/// let mut v = vec![3, 5, 4];
/// v.extend_from_array([] as [i32; 0]);
/// assert_eq!(v, [3, 5, 4]);
/// ```
///
/// # Note to Implementors
///
/// The default implementation of this will pass the array iterator to your `extend` implementation,
/// which is likely optimal for many cases.
///
/// You should override this, however, if you can take advantage of the array elements being contiguous in memory.
/// (`Vec<T>` does, for example, but there's no way to do so in `LinkedList<T>`.)
#[unstable(feature = "extend_from_array", issue = "88888888")]
fn extend_from_array<const N: usize>(&mut self, array: [A; N]) {
self.extend(crate::array::IntoIter::new(array));
}

/// Reserves capacity in a collection for the given number of additional elements.
///
/// The default implementation does nothing.
Expand Down
25 changes: 25 additions & 0 deletions src/test/codegen/vec-extend_from_array.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// compile-flags: -O -C panic=abort
// ignore-debug: the debug assertions get in the way

#![crate_type = "lib"]
#![feature(extend_from_array)]

// This test is here to ensure that LLVM is optimizing out the `ManuallyDrop` construction,
// since we really don't want to copy the whole array to the stack and then again to the Vec.

// CHECK-LABEL: @vec_extend_from_array_demo
#[no_mangle]
pub fn vec_extend_from_array_demo(v: &mut Vec<String>, a: [String; 400]) {
// CHECK-NOT: alloca
// CHECK: call alloc::vec::Vec<T,A>::reserve
// CHECK-NOT: alloca
// CHECK: call void @llvm.memcpy
// CHECK-NOT: alloca
// CHECK: ret
v.extend_from_array(a);
}

// No validation against this one; it just keeps `reserve` from having only a single caller.
pub fn please_do_not_inline_the_reserve_call_llvm(v: &mut Vec<String>, s: String) {
v.push(s);
}