Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit a86fd0f

Browse files
committedJul 7, 2024
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. It may seem that 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, it is impossible due to limitations of `min_specialization`. 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 5569ece commit a86fd0f

File tree

5 files changed

+160
-26
lines changed

5 files changed

+160
-26
lines changed
 

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

+30
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,20 @@ impl<T, A: Allocator> VecDeque<T, A> {
164164
self.buf.ptr()
165165
}
166166

167+
/// Appends an element to the buffer.
168+
///
169+
/// # Safety
170+
///
171+
/// May only be called if `deque.len() < deque.capacity()`
172+
#[inline]
173+
unsafe fn push_unchecked(&mut self, element: T) {
174+
// SAFETY: Because of the precondition, it's guaranteed that there is space
175+
// in the logical array after the last element.
176+
unsafe { self.buffer_write(self.to_physical_idx(self.len), element) };
177+
// This can't overflow because `deque.len() < deque.capacity() <= usize::MAX`.
178+
self.len += 1;
179+
}
180+
167181
/// Moves an element out of the buffer
168182
#[inline]
169183
unsafe fn buffer_read(&mut self, off: usize) -> T {
@@ -2911,6 +2925,14 @@ impl<T, A: Allocator> Extend<T> for VecDeque<T, A> {
29112925
fn extend_reserve(&mut self, additional: usize) {
29122926
self.reserve(additional);
29132927
}
2928+
2929+
#[inline]
2930+
unsafe fn extend_one_unchecked(&mut self, item: T) {
2931+
// SAFETY: Our preconditions ensure the space has been reserved, and `extend_reserve` is implemented correctly.
2932+
unsafe {
2933+
self.push_unchecked(item);
2934+
}
2935+
}
29142936
}
29152937

29162938
#[stable(feature = "extend_ref", since = "1.2.0")]
@@ -2928,6 +2950,14 @@ impl<'a, T: 'a + Copy, A: Allocator> Extend<&'a T> for VecDeque<T, A> {
29282950
fn extend_reserve(&mut self, additional: usize) {
29292951
self.reserve(additional);
29302952
}
2953+
2954+
#[inline]
2955+
unsafe fn extend_one_unchecked(&mut self, &item: &'a T) {
2956+
// SAFETY: Our preconditions ensure the space has been reserved, and `extend_reserve` is implemented correctly.
2957+
unsafe {
2958+
self.push_unchecked(item);
2959+
}
2960+
}
29312961
}
29322962

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

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

+2-11
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
}

‎alloc/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
#![feature(error_generic_member_access)]
125125
#![feature(exact_size_is_empty)]
126126
#![feature(extend_one)]
127+
#![feature(extend_one_unchecked)]
127128
#![feature(fmt_internals)]
128129
#![feature(fn_traits)]
129130
#![feature(hasher_prefixfree_extras)]

‎alloc/src/vec/mod.rs

+20
Original file line numberDiff line numberDiff line change
@@ -3048,6 +3048,16 @@ impl<T, A: Allocator> Extend<T> for Vec<T, A> {
30483048
fn extend_reserve(&mut self, additional: usize) {
30493049
self.reserve(additional);
30503050
}
3051+
3052+
#[inline]
3053+
unsafe fn extend_one_unchecked(&mut self, item: T) {
3054+
// SAFETY: Our preconditions ensure the space has been reserved, and `extend_reserve` is implemented correctly.
3055+
unsafe {
3056+
let len = self.len();
3057+
ptr::write(self.as_mut_ptr().add(len), item);
3058+
self.set_len(len + 1);
3059+
}
3060+
}
30513061
}
30523062

30533063
impl<T, A: Allocator> Vec<T, A> {
@@ -3244,6 +3254,16 @@ impl<'a, T: Copy + 'a, A: Allocator> Extend<&'a T> for Vec<T, A> {
32443254
fn extend_reserve(&mut self, additional: usize) {
32453255
self.reserve(additional);
32463256
}
3257+
3258+
#[inline]
3259+
unsafe fn extend_one_unchecked(&mut self, &item: &'a T) {
3260+
// SAFETY: Our preconditions ensure the space has been reserved, and `extend_reserve` is implemented correctly.
3261+
unsafe {
3262+
let len = self.len();
3263+
ptr::write(self.as_mut_ptr().add(len), item);
3264+
self.set_len(len + 1);
3265+
}
3266+
}
32473267
}
32483268

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

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

+107-15
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
@@ -460,6 +462,27 @@ pub trait Extend<A> {
460462
fn extend_reserve(&mut self, additional: usize) {
461463
let _ = additional;
462464
}
465+
466+
/// Extends a collection with one element, without checking there is enough capacity for it.
467+
///
468+
/// # Safety
469+
///
470+
/// **For callers:** This must only be called when we know the collection has enough capacity
471+
/// to contain the new item, for example because we previously called `extend_reserve`.
472+
///
473+
/// **For implementors:** For a collection to unsafely rely on this method's safety precondition (that is,
474+
/// invoke UB if they are violated), it must implement `extend_reserve` correctly. In other words,
475+
/// callers may assume that if they `extend_reserve`ed enough space they can call this method.
476+
477+
// This method is for internal usage only. It is only on the trait because of specialization's limitations.
478+
#[unstable(feature = "extend_one_unchecked", issue = "none")]
479+
#[doc(hidden)]
480+
unsafe fn extend_one_unchecked(&mut self, item: A)
481+
where
482+
Self: Sized,
483+
{
484+
self.extend_one(item);
485+
}
463486
}
464487

465488
#[stable(feature = "extend_for_unit", since = "1.28.0")]
@@ -499,33 +522,102 @@ where
499522
fn extend<T: IntoIterator<Item = (A, B)>>(&mut self, into_iter: T) {
500523
let (a, b) = self;
501524
let iter = into_iter.into_iter();
525+
SpecTupleExtend::extend(iter, a, b);
526+
}
527+
528+
fn extend_one(&mut self, item: (A, B)) {
529+
self.0.extend_one(item.0);
530+
self.1.extend_one(item.1);
531+
}
532+
533+
fn extend_reserve(&mut self, additional: usize) {
534+
self.0.extend_reserve(additional);
535+
self.1.extend_reserve(additional);
536+
}
537+
538+
unsafe fn extend_one_unchecked(&mut self, item: (A, B)) {
539+
// SAFETY: Those are our safety preconditions, and we correctly forward `extend_reserve`.
540+
unsafe {
541+
self.0.extend_one_unchecked(item.0);
542+
self.1.extend_one_unchecked(item.1);
543+
}
544+
}
545+
}
546+
547+
fn default_extend_tuple<A, B, ExtendA, ExtendB>(
548+
iter: impl Iterator<Item = (A, B)>,
549+
a: &mut ExtendA,
550+
b: &mut ExtendB,
551+
) where
552+
ExtendA: Extend<A>,
553+
ExtendB: Extend<B>,
554+
{
555+
fn extend<'a, A, B>(
556+
a: &'a mut impl Extend<A>,
557+
b: &'a mut impl Extend<B>,
558+
) -> impl FnMut((), (A, B)) + 'a {
559+
move |(), (t, u)| {
560+
a.extend_one(t);
561+
b.extend_one(u);
562+
}
563+
}
564+
565+
let (lower_bound, _) = iter.size_hint();
566+
if lower_bound > 0 {
567+
a.extend_reserve(lower_bound);
568+
b.extend_reserve(lower_bound);
569+
}
570+
571+
iter.fold((), extend(a, b));
572+
}
573+
574+
trait SpecTupleExtend<A, B> {
575+
fn extend(self, a: &mut A, b: &mut B);
576+
}
502577

578+
impl<A, B, ExtendA, ExtendB, Iter> SpecTupleExtend<ExtendA, ExtendB> for Iter
579+
where
580+
ExtendA: Extend<A>,
581+
ExtendB: Extend<B>,
582+
Iter: Iterator<Item = (A, B)>,
583+
{
584+
default fn extend(self, a: &mut ExtendA, b: &mut ExtendB) {
585+
default_extend_tuple(self, a, b);
586+
}
587+
}
588+
589+
impl<A, B, ExtendA, ExtendB, Iter> SpecTupleExtend<ExtendA, ExtendB> for Iter
590+
where
591+
ExtendA: Extend<A>,
592+
ExtendB: Extend<B>,
593+
Iter: TrustedLen<Item = (A, B)>,
594+
{
595+
fn extend(self, a: &mut ExtendA, b: &mut ExtendB) {
503596
fn extend<'a, A, B>(
504597
a: &'a mut impl Extend<A>,
505598
b: &'a mut impl Extend<B>,
506599
) -> impl FnMut((), (A, B)) + 'a {
507-
move |(), (t, u)| {
508-
a.extend_one(t);
509-
b.extend_one(u);
600+
// SAFETY: We reserve enough space for the `size_hint`, and the iterator is `TrustedLen`
601+
// so its `size_hint` is exact.
602+
move |(), (t, u)| unsafe {
603+
a.extend_one_unchecked(t);
604+
b.extend_one_unchecked(u);
510605
}
511606
}
512607

513-
let (lower_bound, _) = iter.size_hint();
608+
let (lower_bound, upper_bound) = self.size_hint();
609+
610+
if upper_bound.is_none() {
611+
// We cannot reserve more than `usize::MAX` items, and this is likely to go out of memory anyway.
612+
default_extend_tuple(self, a, b);
613+
return;
614+
}
615+
514616
if lower_bound > 0 {
515617
a.extend_reserve(lower_bound);
516618
b.extend_reserve(lower_bound);
517619
}
518620

519-
iter.fold((), extend(a, b));
520-
}
521-
522-
fn extend_one(&mut self, item: (A, B)) {
523-
self.0.extend_one(item.0);
524-
self.1.extend_one(item.1);
525-
}
526-
527-
fn extend_reserve(&mut self, additional: usize) {
528-
self.0.extend_reserve(additional);
529-
self.1.extend_reserve(additional);
621+
self.fold((), extend(a, b));
530622
}
531623
}

0 commit comments

Comments
 (0)
Failed to load comments.