Skip to content

Commit 92bfcd2

Browse files
committed
implementing fallible allocation API (try_reserve) for Vec, String and HashMap
1 parent fab632f commit 92bfcd2

16 files changed

+1056
-72
lines changed

Diff for: src/liballoc/allocator.rs

+18
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,24 @@ impl fmt::Display for CannotReallocInPlace {
373373
}
374374
}
375375

376+
/// Augments `AllocErr` with a CapacityOverflow variant.
377+
#[derive(Clone, PartialEq, Eq, Debug)]
378+
#[unstable(feature = "try_reserve", reason = "new API", issue="48043")]
379+
pub enum CollectionAllocErr {
380+
/// Error due to the computed capacity exceeding the collection's maximum
381+
/// (usually `isize::MAX` bytes).
382+
CapacityOverflow,
383+
/// Error due to the allocator (see the `AllocErr` type's docs).
384+
AllocErr(AllocErr),
385+
}
386+
387+
#[unstable(feature = "try_reserve", reason = "new API", issue="48043")]
388+
impl From<AllocErr> for CollectionAllocErr {
389+
fn from(err: AllocErr) -> Self {
390+
CollectionAllocErr::AllocErr(err)
391+
}
392+
}
393+
376394
/// An implementation of `Alloc` can allocate, reallocate, and
377395
/// deallocate arbitrary blocks of data described via `Layout`.
378396
///

Diff for: src/liballoc/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@
117117
#![feature(staged_api)]
118118
#![feature(str_internals)]
119119
#![feature(trusted_len)]
120+
#![feature(try_reserve)]
120121
#![feature(unboxed_closures)]
121122
#![feature(unicode)]
122123
#![feature(unsize)]

Diff for: src/liballoc/raw_vec.rs

+60-42
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use core::ptr::{self, Unique};
1515
use core::slice;
1616
use heap::{Alloc, Layout, Heap};
1717
use super::boxed::Box;
18+
use super::allocator::CollectionAllocErr;
19+
use super::allocator::CollectionAllocErr::*;
1820

1921
/// A low-level utility for more ergonomically allocating, reallocating, and deallocating
2022
/// a buffer of memory on the heap without having to worry about all the corner cases
@@ -84,7 +86,7 @@ impl<T, A: Alloc> RawVec<T, A> {
8486
let elem_size = mem::size_of::<T>();
8587

8688
let alloc_size = cap.checked_mul(elem_size).expect("capacity overflow");
87-
alloc_guard(alloc_size);
89+
alloc_guard(alloc_size).expect("capacity overflow");
8890

8991
// handles ZSTs and `cap = 0` alike
9092
let ptr = if alloc_size == 0 {
@@ -308,7 +310,7 @@ impl<T, A: Alloc> RawVec<T, A> {
308310
let new_cap = 2 * self.cap;
309311
let new_size = new_cap * elem_size;
310312
let new_layout = Layout::from_size_align_unchecked(new_size, cur.align());
311-
alloc_guard(new_size);
313+
alloc_guard(new_size).expect("capacity overflow");
312314
let ptr_res = self.a.realloc(self.ptr.as_ptr() as *mut u8,
313315
cur,
314316
new_layout);
@@ -367,7 +369,7 @@ impl<T, A: Alloc> RawVec<T, A> {
367369
// overflow and the alignment is sufficiently small.
368370
let new_cap = 2 * self.cap;
369371
let new_size = new_cap * elem_size;
370-
alloc_guard(new_size);
372+
alloc_guard(new_size).expect("capacity overflow");
371373
let ptr = self.ptr() as *mut _;
372374
let new_layout = Layout::from_size_align_unchecked(new_size, old_layout.align());
373375
match self.a.grow_in_place(ptr, old_layout, new_layout) {
@@ -403,7 +405,9 @@ impl<T, A: Alloc> RawVec<T, A> {
403405
/// # Aborts
404406
///
405407
/// Aborts on OOM
406-
pub fn reserve_exact(&mut self, used_cap: usize, needed_extra_cap: usize) {
408+
pub fn try_reserve_exact(&mut self, used_cap: usize, needed_extra_cap: usize)
409+
-> Result<(), CollectionAllocErr> {
410+
407411
unsafe {
408412
// NOTE: we don't early branch on ZSTs here because we want this
409413
// to actually catch "asking for more than usize::MAX" in that case.
@@ -413,43 +417,50 @@ impl<T, A: Alloc> RawVec<T, A> {
413417
// Don't actually need any more capacity.
414418
// Wrapping in case they gave a bad `used_cap`.
415419
if self.cap().wrapping_sub(used_cap) >= needed_extra_cap {
416-
return;
420+
return Ok(());
417421
}
418422

419423
// Nothing we can really do about these checks :(
420-
let new_cap = used_cap.checked_add(needed_extra_cap).expect("capacity overflow");
421-
let new_layout = match Layout::array::<T>(new_cap) {
422-
Some(layout) => layout,
423-
None => panic!("capacity overflow"),
424-
};
425-
alloc_guard(new_layout.size());
424+
let new_cap = used_cap.checked_add(needed_extra_cap).ok_or(CapacityOverflow)?;
425+
let new_layout = Layout::array::<T>(new_cap).ok_or(CapacityOverflow)?;
426+
427+
alloc_guard(new_layout.size())?;
428+
426429
let res = match self.current_layout() {
427430
Some(layout) => {
428431
let old_ptr = self.ptr.as_ptr() as *mut u8;
429432
self.a.realloc(old_ptr, layout, new_layout)
430433
}
431434
None => self.a.alloc(new_layout),
432435
};
433-
let uniq = match res {
434-
Ok(ptr) => Unique::new_unchecked(ptr as *mut T),
435-
Err(e) => self.a.oom(e),
436-
};
437-
self.ptr = uniq;
436+
437+
self.ptr = Unique::new_unchecked(res? as *mut T);
438438
self.cap = new_cap;
439+
440+
Ok(())
439441
}
440442
}
441443

444+
pub fn reserve_exact(&mut self, used_cap: usize, needed_extra_cap: usize) {
445+
match self.try_reserve_exact(used_cap, needed_extra_cap) {
446+
Err(CapacityOverflow) => panic!("capacity overflow"),
447+
Err(AllocErr(e)) => self.a.oom(e),
448+
Ok(()) => { /* yay */ }
449+
}
450+
}
451+
442452
/// Calculates the buffer's new size given that it'll hold `used_cap +
443453
/// needed_extra_cap` elements. This logic is used in amortized reserve methods.
444454
/// Returns `(new_capacity, new_alloc_size)`.
445-
fn amortized_new_size(&self, used_cap: usize, needed_extra_cap: usize) -> usize {
455+
fn amortized_new_size(&self, used_cap: usize, needed_extra_cap: usize)
456+
-> Result<usize, CollectionAllocErr> {
457+
446458
// Nothing we can really do about these checks :(
447-
let required_cap = used_cap.checked_add(needed_extra_cap)
448-
.expect("capacity overflow");
459+
let required_cap = used_cap.checked_add(needed_extra_cap).ok_or(CapacityOverflow)?;
449460
// Cannot overflow, because `cap <= isize::MAX`, and type of `cap` is `usize`.
450461
let double_cap = self.cap * 2;
451462
// `double_cap` guarantees exponential growth.
452-
cmp::max(double_cap, required_cap)
463+
Ok(cmp::max(double_cap, required_cap))
453464
}
454465

455466
/// Ensures that the buffer contains at least enough space to hold
@@ -504,8 +515,9 @@ impl<T, A: Alloc> RawVec<T, A> {
504515
/// # vector.push_all(&[1, 3, 5, 7, 9]);
505516
/// # }
506517
/// ```
507-
pub fn reserve(&mut self, used_cap: usize, needed_extra_cap: usize) {
508-
unsafe {
518+
pub fn try_reserve(&mut self, used_cap: usize, needed_extra_cap: usize)
519+
-> Result<(), CollectionAllocErr> {
520+
unsafe {
509521
// NOTE: we don't early branch on ZSTs here because we want this
510522
// to actually catch "asking for more than usize::MAX" in that case.
511523
// If we make it past the first branch then we are guaranteed to
@@ -514,33 +526,38 @@ impl<T, A: Alloc> RawVec<T, A> {
514526
// Don't actually need any more capacity.
515527
// Wrapping in case they give a bad `used_cap`
516528
if self.cap().wrapping_sub(used_cap) >= needed_extra_cap {
517-
return;
529+
return Ok(());
518530
}
519531

520-
let new_cap = self.amortized_new_size(used_cap, needed_extra_cap);
532+
let new_cap = self.amortized_new_size(used_cap, needed_extra_cap)?;
533+
let new_layout = Layout::array::<T>(new_cap).ok_or(CapacityOverflow)?;
534+
535+
// FIXME: may crash and burn on over-reserve
536+
alloc_guard(new_layout.size())?;
521537

522-
let new_layout = match Layout::array::<T>(new_cap) {
523-
Some(layout) => layout,
524-
None => panic!("capacity overflow"),
525-
};
526-
// FIXME: may crash and burn on over-reserve
527-
alloc_guard(new_layout.size());
528538
let res = match self.current_layout() {
529539
Some(layout) => {
530540
let old_ptr = self.ptr.as_ptr() as *mut u8;
531541
self.a.realloc(old_ptr, layout, new_layout)
532542
}
533543
None => self.a.alloc(new_layout),
534544
};
535-
let uniq = match res {
536-
Ok(ptr) => Unique::new_unchecked(ptr as *mut T),
537-
Err(e) => self.a.oom(e),
538-
};
539-
self.ptr = uniq;
545+
546+
self.ptr = Unique::new_unchecked(res? as *mut T);
540547
self.cap = new_cap;
548+
549+
Ok(())
541550
}
542551
}
543552

553+
/// The same as try_reserve, but errors are lowered to a call to oom().
554+
pub fn reserve(&mut self, used_cap: usize, needed_extra_cap: usize) {
555+
match self.try_reserve(used_cap, needed_extra_cap) {
556+
Err(CapacityOverflow) => panic!("capacity overflow"),
557+
Err(AllocErr(e)) => self.a.oom(e),
558+
Ok(()) => { /* yay */ }
559+
}
560+
}
544561
/// Attempts to ensure that the buffer contains at least enough space to hold
545562
/// `used_cap + needed_extra_cap` elements. If it doesn't already have
546563
/// enough capacity, will reallocate in place enough space plus comfortable slack
@@ -576,7 +593,8 @@ impl<T, A: Alloc> RawVec<T, A> {
576593
return false;
577594
}
578595

579-
let new_cap = self.amortized_new_size(used_cap, needed_extra_cap);
596+
let new_cap = self.amortized_new_size(used_cap, needed_extra_cap)
597+
.expect("capacity overflow");
580598

581599
// Here, `cap < used_cap + needed_extra_cap <= new_cap`
582600
// (regardless of whether `self.cap - used_cap` wrapped).
@@ -585,7 +603,7 @@ impl<T, A: Alloc> RawVec<T, A> {
585603
let ptr = self.ptr() as *mut _;
586604
let new_layout = Layout::new::<T>().repeat(new_cap).unwrap().0;
587605
// FIXME: may crash and burn on over-reserve
588-
alloc_guard(new_layout.size());
606+
alloc_guard(new_layout.size()).expect("capacity overflow");
589607
match self.a.grow_in_place(ptr, old_layout, new_layout) {
590608
Ok(_) => {
591609
self.cap = new_cap;
@@ -709,14 +727,14 @@ unsafe impl<#[may_dangle] T, A: Alloc> Drop for RawVec<T, A> {
709727
// all 4GB in user-space. e.g. PAE or x32
710728

711729
#[inline]
712-
fn alloc_guard(alloc_size: usize) {
713-
if mem::size_of::<usize>() < 8 {
714-
assert!(alloc_size <= ::core::isize::MAX as usize,
715-
"capacity overflow");
730+
fn alloc_guard(alloc_size: usize) -> Result<(), CollectionAllocErr> {
731+
if mem::size_of::<usize>() < 8 && alloc_size > ::core::isize::MAX as usize {
732+
Err(CapacityOverflow)
733+
} else {
734+
Ok(())
716735
}
717736
}
718737

719-
720738
#[cfg(test)]
721739
mod tests {
722740
use super::*;

Diff for: src/liballoc/string.rs

+74
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ use Bound::{Excluded, Included, Unbounded};
7171
use str::{self, from_boxed_utf8_unchecked, FromStr, Utf8Error, Chars};
7272
use vec::Vec;
7373
use boxed::Box;
74+
use super::allocator::CollectionAllocErr;
7475

7576
/// A UTF-8 encoded, growable string.
7677
///
@@ -920,6 +921,79 @@ impl String {
920921
self.vec.reserve_exact(additional)
921922
}
922923

924+
/// Tries to reserve capacity for at least `additional` more elements to be inserted
925+
/// in the given `String`. The collection may reserve more space to avoid
926+
/// frequent reallocations. After calling `reserve`, capacity will be
927+
/// greater than or equal to `self.len() + additional`. Does nothing if
928+
/// capacity is already sufficient.
929+
///
930+
/// # Errors
931+
///
932+
/// If the capacity overflows, or the allocator reports a failure, then an error
933+
/// is returned.
934+
///
935+
/// # Examples
936+
///
937+
/// ```
938+
/// #![feature(try_reserve)]
939+
/// use std::collections::CollectionAllocErr;
940+
///
941+
/// fn process_data(data: &str) -> Result<String, CollectionAllocErr> {
942+
/// let mut output = String::new();
943+
///
944+
/// // Pre-reserve the memory, exiting if we can't
945+
/// output.try_reserve(data.len())?;
946+
///
947+
/// // Now we know this can't OOM in the middle of our complex work
948+
/// output.push_str(data);
949+
///
950+
/// Ok(output)
951+
/// }
952+
/// # process_data("rust").expect("why is the test harness OOMing on 4 bytes?");
953+
/// ```
954+
#[unstable(feature = "try_reserve", reason = "new API", issue="48043")]
955+
pub fn try_reserve(&mut self, additional: usize) -> Result<(), CollectionAllocErr> {
956+
self.vec.try_reserve(additional)
957+
}
958+
959+
/// Tries to reserves the minimum capacity for exactly `additional` more elements to
960+
/// be inserted in the given `String`. After calling `reserve_exact`,
961+
/// capacity will be greater than or equal to `self.len() + additional`.
962+
/// Does nothing if the capacity is already sufficient.
963+
///
964+
/// Note that the allocator may give the collection more space than it
965+
/// requests. Therefore capacity can not be relied upon to be precisely
966+
/// minimal. Prefer `reserve` if future insertions are expected.
967+
///
968+
/// # Errors
969+
///
970+
/// If the capacity overflows, or the allocator reports a failure, then an error
971+
/// is returned.
972+
///
973+
/// # Examples
974+
///
975+
/// ```
976+
/// #![feature(try_reserve)]
977+
/// use std::collections::CollectionAllocErr;
978+
///
979+
/// fn process_data(data: &str) -> Result<String, CollectionAllocErr> {
980+
/// let mut output = String::new();
981+
///
982+
/// // Pre-reserve the memory, exiting if we can't
983+
/// output.try_reserve(data.len())?;
984+
///
985+
/// // Now we know this can't OOM in the middle of our complex work
986+
/// output.push_str(data);
987+
///
988+
/// Ok(output)
989+
/// }
990+
/// # process_data("rust").expect("why is the test harness OOMing on 4 bytes?");
991+
/// ```
992+
#[unstable(feature = "try_reserve", reason = "new API", issue="48043")]
993+
pub fn try_reserve_exact(&mut self, additional: usize) -> Result<(), CollectionAllocErr> {
994+
self.vec.try_reserve_exact(additional)
995+
}
996+
923997
/// Shrinks the capacity of this `String` to match its length.
924998
///
925999
/// # Examples

Diff for: src/liballoc/tests/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#![feature(splice)]
2727
#![feature(str_escape)]
2828
#![feature(string_retain)]
29+
#![feature(try_reserve)]
2930
#![feature(unboxed_closures)]
3031
#![feature(unicode)]
3132
#![feature(exact_chunks)]

0 commit comments

Comments
 (0)