Skip to content

Commit 44c0087

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 a semi-public (`#[doc(hidden)]` public, so collections outside of core can implement it) specialization trait that has a method (`extend_one_unchecked()`) that doesn't check for growth. Then specialize `Extend for (A, B)` for `TrustedLen` to call it, and specialize it for collections (only `Vec` and `VecDequq` from the collection in the standard library could benefit from this) An alternative way of implementing this is to have this method on `Extend` directly, but this was rejected by @the8472 in a review (rust-lang#123253 (comment)). I didn't mark the trait as unsafe; a concern that may arise from that 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 44c0087

File tree

7 files changed

+187
-28
lines changed

7 files changed

+187
-28
lines changed

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

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use core::cmp::{self, Ordering};
1111
use core::fmt;
1212
use core::hash::{Hash, Hasher};
13-
use core::iter::{repeat_n, repeat_with, ByRefSized};
13+
use core::iter::{repeat_n, repeat_with, ByRefSized, ExtendUnchecked};
1414
use core::mem::{ManuallyDrop, SizedTypeProperties};
1515
use core::ops::{Index, IndexMut, Range, RangeBounds};
1616
use core::ptr;
@@ -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 {
@@ -2837,6 +2851,16 @@ impl<T, A: Allocator> Extend<T> for VecDeque<T, A> {
28372851
}
28382852
}
28392853

2854+
impl<T, A: Allocator> ExtendUnchecked<T> for VecDeque<T, A> {
2855+
#[inline]
2856+
unsafe fn extend_one_unchecked(&mut self, item: T) {
2857+
// SAFETY: Our preconditions ensure the space has been reserved, and `extend_reserve` is implemented correctly.
2858+
unsafe {
2859+
self.push_unchecked(item);
2860+
}
2861+
}
2862+
}
2863+
28402864
#[stable(feature = "extend_ref", since = "1.2.0")]
28412865
impl<'a, T: 'a + Copy, A: Allocator> Extend<&'a T> for VecDeque<T, A> {
28422866
fn extend<I: IntoIterator<Item = &'a T>>(&mut self, iter: I) {
@@ -2854,6 +2878,16 @@ impl<'a, T: 'a + Copy, A: Allocator> Extend<&'a T> for VecDeque<T, A> {
28542878
}
28552879
}
28562880

2881+
impl<'a, T: 'a + Copy, A: Allocator> ExtendUnchecked<&'a T> for VecDeque<T, A> {
2882+
#[inline]
2883+
unsafe fn extend_one_unchecked(&mut self, &item: &'a T) {
2884+
// SAFETY: Our preconditions ensure the space has been reserved, and `extend_reserve` is implemented correctly.
2885+
unsafe {
2886+
self.push_unchecked(item);
2887+
}
2888+
}
2889+
}
2890+
28572891
#[stable(feature = "rust1", since = "1.0.0")]
28582892
impl<T: fmt::Debug, A: Allocator> fmt::Debug for VecDeque<T, A> {
28592893
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

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_unchecked)]
131132
#![feature(fmt_internals)]
132133
#![feature(fn_traits)]
133134
#![feature(generic_nonzero)]

library/alloc/src/vec/mod.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ use core::cmp::Ordering;
5959
use core::fmt;
6060
use core::hash::{Hash, Hasher};
6161
#[cfg(not(no_global_oom_handling))]
62-
use core::iter;
62+
use core::iter::{self, ExtendUnchecked};
6363
use core::marker::PhantomData;
6464
use core::mem::{self, ManuallyDrop, MaybeUninit, SizedTypeProperties};
6565
use core::ops::{self, Index, IndexMut, Range, RangeBounds};
@@ -3000,6 +3000,19 @@ impl<T, A: Allocator> Extend<T> for Vec<T, A> {
30003000
}
30013001
}
30023002

3003+
#[cfg(not(no_global_oom_handling))]
3004+
impl<T, A: Allocator> ExtendUnchecked<T> for Vec<T, A> {
3005+
#[inline]
3006+
unsafe fn extend_one_unchecked(&mut self, item: T) {
3007+
// SAFETY: Our preconditions ensure the space has been reserved, and `extend_reserve` is implemented correctly.
3008+
unsafe {
3009+
let len = self.len();
3010+
ptr::write(self.as_mut_ptr().add(len), item);
3011+
self.set_len(len + 1);
3012+
}
3013+
}
3014+
}
3015+
30033016
impl<T, A: Allocator> Vec<T, A> {
30043017
// leaf method to which various SpecFrom/SpecExtend implementations delegate when
30053018
// they have no further optimizations to apply
@@ -3196,6 +3209,19 @@ impl<'a, T: Copy + 'a, A: Allocator> Extend<&'a T> for Vec<T, A> {
31963209
}
31973210
}
31983211

3212+
#[cfg(not(no_global_oom_handling))]
3213+
impl<'a, T: Copy + 'a, A: Allocator> ExtendUnchecked<&'a T> for Vec<T, A> {
3214+
#[inline]
3215+
unsafe fn extend_one_unchecked(&mut self, &item: &'a T) {
3216+
// SAFETY: Our preconditions ensure the space has been reserved, and `extend_reserve` is implemented correctly.
3217+
unsafe {
3218+
let len = self.len();
3219+
ptr::write(self.as_mut_ptr().add(len), item);
3220+
self.set_len(len + 1);
3221+
}
3222+
}
3223+
}
3224+
31993225
/// Implements comparison of vectors, [lexicographically](Ord#lexicographical-comparison).
32003226
#[stable(feature = "rust1", since = "1.0.0")]
32013227
impl<T, A1, A2> PartialOrd<Vec<T, A2>> for Vec<T, A1>

library/core/src/iter/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,9 @@ pub use self::sources::{repeat_with, RepeatWith};
413413
#[stable(feature = "iter_successors", since = "1.34.0")]
414414
pub use self::sources::{successors, Successors};
415415

416+
#[doc(hidden)]
417+
#[unstable(feature = "extend_unchecked", issue = "none")]
418+
pub use self::traits::ExtendUnchecked;
416419
#[stable(feature = "fused", since = "1.26.0")]
417420
pub use self::traits::FusedIterator;
418421
#[unstable(issue = "none", feature = "inplace_iteration")]

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

Lines changed: 116 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
@@ -429,6 +431,30 @@ pub trait Extend<A> {
429431
}
430432
}
431433

434+
// This trait is exported so collections outside of `core` can implement it.
435+
#[doc(hidden)]
436+
#[unstable(feature = "extend_unchecked", issue = "none")]
437+
pub trait ExtendUnchecked<A>: Extend<A> {
438+
/// Extends a collection with one element, without checking there is enough capacity for it.
439+
///
440+
/// **Note:** For a collection to unsafely rely on this method's safety precondition (that is,
441+
/// invoke UB if they are violated), it must implement `extend_reserve` correctly. In other words,
442+
/// callers may assume that if they `extend_reserve`ed enough space they can call this method.
443+
///
444+
/// # Safety
445+
///
446+
/// This must only be called when we know the collection has enough capacity to contain the new item,
447+
/// for example because we previously called `extend_reserve`.
448+
unsafe fn extend_one_unchecked(&mut self, item: A);
449+
}
450+
451+
#[unstable(feature = "extend_unchecked", issue = "none")]
452+
impl<A, T: Extend<A>> ExtendUnchecked<A> for T {
453+
default unsafe fn extend_one_unchecked(&mut self, item: A) {
454+
self.extend_one(item);
455+
}
456+
}
457+
432458
#[stable(feature = "extend_for_unit", since = "1.28.0")]
433459
impl Extend<()> for () {
434460
fn extend<T: IntoIterator<Item = ()>>(&mut self, iter: T) {
@@ -466,33 +492,108 @@ where
466492
fn extend<T: IntoIterator<Item = (A, B)>>(&mut self, into_iter: T) {
467493
let (a, b) = self;
468494
let iter = into_iter.into_iter();
495+
SpecTupleExtend::extend(iter, a, b);
496+
}
497+
498+
fn extend_one(&mut self, item: (A, B)) {
499+
self.0.extend_one(item.0);
500+
self.1.extend_one(item.1);
501+
}
469502

503+
fn extend_reserve(&mut self, additional: usize) {
504+
self.0.extend_reserve(additional);
505+
self.1.extend_reserve(additional);
506+
}
507+
}
508+
509+
impl<A, B, ExtendA, ExtendB> ExtendUnchecked<(A, B)> for (ExtendA, ExtendB)
510+
where
511+
ExtendA: Extend<A>,
512+
ExtendB: Extend<B>,
513+
{
514+
unsafe fn extend_one_unchecked(&mut self, item: (A, B)) {
515+
// SAFETY: Those are our safety preconditions, and we correctly forward `extend_reserve`.
516+
unsafe {
517+
self.0.extend_one_unchecked(item.0);
518+
self.1.extend_one_unchecked(item.1);
519+
}
520+
}
521+
}
522+
523+
fn default_extend_tuple<A, B, ExtendA, ExtendB>(
524+
iter: impl Iterator<Item = (A, B)>,
525+
a: &mut ExtendA,
526+
b: &mut ExtendB,
527+
) where
528+
ExtendA: Extend<A>,
529+
ExtendB: Extend<B>,
530+
{
531+
fn extend<'a, A, B>(
532+
a: &'a mut impl Extend<A>,
533+
b: &'a mut impl Extend<B>,
534+
) -> impl FnMut((), (A, B)) + 'a {
535+
move |(), (t, u)| {
536+
a.extend_one(t);
537+
b.extend_one(u);
538+
}
539+
}
540+
541+
let (lower_bound, _) = iter.size_hint();
542+
if lower_bound > 0 {
543+
a.extend_reserve(lower_bound);
544+
b.extend_reserve(lower_bound);
545+
}
546+
547+
iter.fold((), extend(a, b));
548+
}
549+
550+
trait SpecTupleExtend<A, B> {
551+
fn extend(self, a: &mut A, b: &mut B);
552+
}
553+
554+
impl<A, B, ExtendA, ExtendB, Iter> SpecTupleExtend<ExtendA, ExtendB> for Iter
555+
where
556+
ExtendA: Extend<A>,
557+
ExtendB: Extend<B>,
558+
Iter: Iterator<Item = (A, B)>,
559+
{
560+
default fn extend(self, a: &mut ExtendA, b: &mut ExtendB) {
561+
default_extend_tuple(self, a, b);
562+
}
563+
}
564+
565+
impl<A, B, ExtendA, ExtendB, Iter> SpecTupleExtend<ExtendA, ExtendB> for Iter
566+
where
567+
ExtendA: Extend<A>,
568+
ExtendB: Extend<B>,
569+
Iter: TrustedLen<Item = (A, B)>,
570+
{
571+
fn extend(self, a: &mut ExtendA, b: &mut ExtendB) {
470572
fn extend<'a, A, B>(
471573
a: &'a mut impl Extend<A>,
472574
b: &'a mut impl Extend<B>,
473575
) -> impl FnMut((), (A, B)) + 'a {
474-
move |(), (t, u)| {
475-
a.extend_one(t);
476-
b.extend_one(u);
576+
// SAFETY: We reserve enough space for the `size_hint`, and the iterator is `TrustedLen`
577+
// so its `size_hint` is exact.
578+
move |(), (t, u)| unsafe {
579+
a.extend_one_unchecked(t);
580+
b.extend_one_unchecked(u);
477581
}
478582
}
479583

480-
let (lower_bound, _) = iter.size_hint();
584+
let (lower_bound, upper_bound) = self.size_hint();
585+
586+
if upper_bound.is_none() {
587+
// We cannot reserve more than `usize::MAX` items, and this is likely to go out of memory anyway.
588+
default_extend_tuple(self, a, b);
589+
return;
590+
}
591+
481592
if lower_bound > 0 {
482593
a.extend_reserve(lower_bound);
483594
b.extend_reserve(lower_bound);
484595
}
485596

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);
597+
self.fold((), extend(a, b));
497598
}
498599
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ pub use self::{
1616
marker::{FusedIterator, TrustedLen},
1717
};
1818

19+
#[doc(hidden)]
20+
#[unstable(feature = "extend_unchecked", issue = "none")]
21+
pub use self::collect::ExtendUnchecked;
1922
#[unstable(issue = "none", feature = "inplace_iteration")]
2023
pub use self::marker::InPlaceIterable;
2124
#[unstable(issue = "none", feature = "trusted_fused")]

0 commit comments

Comments
 (0)