Skip to content

Commit c4bfa05

Browse files
Specialize TrustedLen for Iterator::unzip()
Don't check the capacity every time (and also for `Extend` for tuples, as this is how `unzip()` is implemented). I did this with an unsafe method on `Extend` that doesn't check for growth (`extend_one_unchecked()`). I've marked it as perma-unstable currently, although we may want to expose it in the future so collections outside of std can benefit from it. Then specialize `Extend for (A, B)` for `TrustedLen` to call it. An alternative way of implementing this is to have a semi-public trait (`#[doc(hidden)]` public, so collections outside of core can implement it) for `extend()` inside tuples, and specialize it from collections. However: 1. This looks more complex to me. 2. This prohibits the option of exposing this somewhen to collections outside of std, as we never expose specializations. A concern that may arise with the current approach is that implementing `extend_one_unchecked()` correctly must also incur implementing `extend_reserve()`, otherwise you can have UB. This is a somewhat non-local safety invariant. However, I believe this is fine, since to have actual UB you must have unsafe code inside your `extend_one_unchecked()` that makes incorrect assumption, *and* not implement `extend_reserve()`. I've also documented this requirement.
1 parent 8df7e72 commit c4bfa05

File tree

5 files changed

+155
-26
lines changed

5 files changed

+155
-26
lines changed

library/alloc/src/collections/vec_deque/mod.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,20 @@ impl<T, A: Allocator> VecDeque<T, A> {
160160
self.buf.ptr()
161161
}
162162

163+
/// Appends an element to the buffer.
164+
///
165+
/// # Safety
166+
///
167+
/// May only be called if `deque.len() < deque.capacity()`
168+
#[inline]
169+
unsafe fn push_unchecked(&mut self, element: T) {
170+
// SAFETY: Because of the precondition, it's guaranteed that there is space
171+
// in the logical array after the last element.
172+
unsafe { self.buffer_write(self.to_physical_idx(self.len), element) };
173+
// This can't overflow because `deque.len() < deque.capacity() <= usize::MAX`.
174+
self.len += 1;
175+
}
176+
163177
/// Moves an element out of the buffer
164178
#[inline]
165179
unsafe fn buffer_read(&mut self, off: usize) -> T {
@@ -2835,6 +2849,14 @@ impl<T, A: Allocator> Extend<T> for VecDeque<T, A> {
28352849
fn extend_reserve(&mut self, additional: usize) {
28362850
self.reserve(additional);
28372851
}
2852+
2853+
#[inline]
2854+
unsafe fn extend_one_unchecked(&mut self, item: T) {
2855+
// SAFETY: Our preconditions ensure the space has been reserved, and `extend_reserve` is implemented correctly.
2856+
unsafe {
2857+
self.push_unchecked(item);
2858+
}
2859+
}
28382860
}
28392861

28402862
#[stable(feature = "extend_ref", since = "1.2.0")]
@@ -2852,6 +2874,14 @@ impl<'a, T: 'a + Copy, A: Allocator> Extend<&'a T> for VecDeque<T, A> {
28522874
fn extend_reserve(&mut self, additional: usize) {
28532875
self.reserve(additional);
28542876
}
2877+
2878+
#[inline]
2879+
unsafe fn extend_one_unchecked(&mut self, &item: &'a T) {
2880+
// SAFETY: Our preconditions ensure the space has been reserved, and `extend_reserve` is implemented correctly.
2881+
unsafe {
2882+
self.push_unchecked(item);
2883+
}
2884+
}
28552885
}
28562886

28572887
#[stable(feature = "rust1", since = "1.0.0")]

library/alloc/src/collections/vec_deque/spec_extend.rs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,29 +21,20 @@ where
2121
// self.push_back(item);
2222
// }
2323

24-
// May only be called if `deque.len() < deque.capacity()`
25-
unsafe fn push_unchecked<T, A: Allocator>(deque: &mut VecDeque<T, A>, element: T) {
26-
// SAFETY: Because of the precondition, it's guaranteed that there is space
27-
// in the logical array after the last element.
28-
unsafe { deque.buffer_write(deque.to_physical_idx(deque.len), element) };
29-
// This can't overflow because `deque.len() < deque.capacity() <= usize::MAX`.
30-
deque.len += 1;
31-
}
32-
3324
while let Some(element) = iter.next() {
3425
let (lower, _) = iter.size_hint();
3526
self.reserve(lower.saturating_add(1));
3627

3728
// SAFETY: We just reserved space for at least one element.
38-
unsafe { push_unchecked(self, element) };
29+
unsafe { self.push_unchecked(element) };
3930

4031
// Inner loop to avoid repeatedly calling `reserve`.
4132
while self.len < self.capacity() {
4233
let Some(element) = iter.next() else {
4334
return;
4435
};
4536
// SAFETY: The loop condition guarantees that `self.len() < self.capacity()`.
46-
unsafe { push_unchecked(self, element) };
37+
unsafe { self.push_unchecked(element) };
4738
}
4839
}
4940
}

library/alloc/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@
128128
#![feature(error_in_core)]
129129
#![feature(exact_size_is_empty)]
130130
#![feature(extend_one)]
131+
#![feature(extend_one_unchecked)]
131132
#![feature(fmt_internals)]
132133
#![feature(fn_traits)]
133134
#![feature(generic_nonzero)]

library/alloc/src/vec/mod.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2998,6 +2998,16 @@ impl<T, A: Allocator> Extend<T> for Vec<T, A> {
29982998
fn extend_reserve(&mut self, additional: usize) {
29992999
self.reserve(additional);
30003000
}
3001+
3002+
#[inline]
3003+
unsafe fn extend_one_unchecked(&mut self, item: T) {
3004+
// SAFETY: Our preconditions ensure the space has been reserved, and `extend_reserve` is implemented correctly.
3005+
unsafe {
3006+
let len = self.len();
3007+
ptr::write(self.as_mut_ptr().add(len), item);
3008+
self.set_len(len + 1);
3009+
}
3010+
}
30013011
}
30023012

30033013
impl<T, A: Allocator> Vec<T, A> {
@@ -3194,6 +3204,16 @@ impl<'a, T: Copy + 'a, A: Allocator> Extend<&'a T> for Vec<T, A> {
31943204
fn extend_reserve(&mut self, additional: usize) {
31953205
self.reserve(additional);
31963206
}
3207+
3208+
#[inline]
3209+
unsafe fn extend_one_unchecked(&mut self, &item: &'a T) {
3210+
// SAFETY: Our preconditions ensure the space has been reserved, and `extend_reserve` is implemented correctly.
3211+
unsafe {
3212+
let len = self.len();
3213+
ptr::write(self.as_mut_ptr().add(len), item);
3214+
self.set_len(len + 1);
3215+
}
3216+
}
31973217
}
31983218

31993219
/// Implements comparison of vectors, [lexicographically](Ord#lexicographical-comparison).

library/core/src/iter/traits/collect.rs

Lines changed: 102 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use super::TrustedLen;
2+
13
/// Conversion from an [`Iterator`].
24
///
35
/// By implementing `FromIterator` for a type, you define how it will be
@@ -427,6 +429,22 @@ pub trait Extend<A> {
427429
fn extend_reserve(&mut self, additional: usize) {
428430
let _ = additional;
429431
}
432+
433+
/// Extends a collection with one element, without checking there is enough capacity for it.
434+
///
435+
/// **Note:** For a collection to unsafely rely on this method's safety precondition (that is,
436+
/// invoke UB if they are violated), it must implement `extend_reserve` correctly. In other words,
437+
/// callers may assume that if they `extend_reserve`ed enough space they can call this method.
438+
///
439+
/// # Safety
440+
///
441+
/// This must only be called when we know the collection has enough capacity to contain the new item,
442+
/// for example because we previously called `extend_reserve`.
443+
#[unstable(feature = "extend_one_unchecked", issue = "none")]
444+
#[doc(hidden)]
445+
unsafe fn extend_one_unchecked(&mut self, item: A) {
446+
self.extend_one(item);
447+
}
430448
}
431449

432450
#[stable(feature = "extend_for_unit", since = "1.28.0")]
@@ -466,33 +484,102 @@ where
466484
fn extend<T: IntoIterator<Item = (A, B)>>(&mut self, into_iter: T) {
467485
let (a, b) = self;
468486
let iter = into_iter.into_iter();
487+
SpecTupleExtend::extend(iter, a, b);
488+
}
489+
490+
fn extend_one(&mut self, item: (A, B)) {
491+
self.0.extend_one(item.0);
492+
self.1.extend_one(item.1);
493+
}
494+
495+
fn extend_reserve(&mut self, additional: usize) {
496+
self.0.extend_reserve(additional);
497+
self.1.extend_reserve(additional);
498+
}
499+
500+
unsafe fn extend_one_unchecked(&mut self, item: (A, B)) {
501+
// SAFETY: Those are our safety preconditions, and we correctly forward `extend_reserve`.
502+
unsafe {
503+
self.0.extend_one_unchecked(item.0);
504+
self.1.extend_one_unchecked(item.1);
505+
}
506+
}
507+
}
508+
509+
fn default_extend_tuple<A, B, ExtendA, ExtendB>(
510+
iter: impl Iterator<Item = (A, B)>,
511+
a: &mut ExtendA,
512+
b: &mut ExtendB,
513+
) where
514+
ExtendA: Extend<A>,
515+
ExtendB: Extend<B>,
516+
{
517+
fn extend<'a, A, B>(
518+
a: &'a mut impl Extend<A>,
519+
b: &'a mut impl Extend<B>,
520+
) -> impl FnMut((), (A, B)) + 'a {
521+
move |(), (t, u)| {
522+
a.extend_one(t);
523+
b.extend_one(u);
524+
}
525+
}
526+
527+
let (lower_bound, _) = iter.size_hint();
528+
if lower_bound > 0 {
529+
a.extend_reserve(lower_bound);
530+
b.extend_reserve(lower_bound);
531+
}
469532

533+
iter.fold((), extend(a, b));
534+
}
535+
536+
trait SpecTupleExtend<A, B> {
537+
fn extend(self, a: &mut A, b: &mut B);
538+
}
539+
540+
impl<A, B, ExtendA, ExtendB, Iter> SpecTupleExtend<ExtendA, ExtendB> for Iter
541+
where
542+
ExtendA: Extend<A>,
543+
ExtendB: Extend<B>,
544+
Iter: Iterator<Item = (A, B)>,
545+
{
546+
default fn extend(self, a: &mut ExtendA, b: &mut ExtendB) {
547+
default_extend_tuple(self, a, b);
548+
}
549+
}
550+
551+
impl<A, B, ExtendA, ExtendB, Iter> SpecTupleExtend<ExtendA, ExtendB> for Iter
552+
where
553+
ExtendA: Extend<A>,
554+
ExtendB: Extend<B>,
555+
Iter: TrustedLen<Item = (A, B)>,
556+
{
557+
fn extend(self, a: &mut ExtendA, b: &mut ExtendB) {
470558
fn extend<'a, A, B>(
471559
a: &'a mut impl Extend<A>,
472560
b: &'a mut impl Extend<B>,
473561
) -> impl FnMut((), (A, B)) + 'a {
474-
move |(), (t, u)| {
475-
a.extend_one(t);
476-
b.extend_one(u);
562+
// SAFETY: We reserve enough space for the `size_hint`, and the iterator is `TrustedLen`
563+
// so its `size_hint` is exact.
564+
move |(), (t, u)| unsafe {
565+
a.extend_one_unchecked(t);
566+
b.extend_one_unchecked(u);
477567
}
478568
}
479569

480-
let (lower_bound, _) = iter.size_hint();
570+
let (lower_bound, upper_bound) = self.size_hint();
571+
572+
if upper_bound.is_none() {
573+
// We cannot reserve more than `usize::MAX` items, and this is likely to go out of memory anyway.
574+
default_extend_tuple(self, a, b);
575+
return;
576+
}
577+
481578
if lower_bound > 0 {
482579
a.extend_reserve(lower_bound);
483580
b.extend_reserve(lower_bound);
484581
}
485582

486-
iter.fold((), extend(a, b));
487-
}
488-
489-
fn extend_one(&mut self, item: (A, B)) {
490-
self.0.extend_one(item.0);
491-
self.1.extend_one(item.1);
492-
}
493-
494-
fn extend_reserve(&mut self, additional: usize) {
495-
self.0.extend_reserve(additional);
496-
self.1.extend_reserve(additional);
583+
self.fold((), extend(a, b));
497584
}
498585
}

0 commit comments

Comments
 (0)