Skip to content

Commit d63807c

Browse files
committed
Allow reclaiming the current allocation
This is based on #680, where it was noted that it is hard to use BytesMut without additional allocations in some circumstances.
1 parent ce8d8a0 commit d63807c

File tree

2 files changed

+106
-0
lines changed

2 files changed

+106
-0
lines changed

src/bytes_mut.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use alloc::{
1111
vec,
1212
vec::Vec,
1313
};
14+
use std::num::NonZeroUsize;
1415

1516
use crate::buf::{IntoIter, UninitSlice};
1617
use crate::bytes::Vtable;
@@ -819,6 +820,70 @@ impl BytesMut {
819820
}
820821
}
821822

823+
/// Attempts to reclaim the whole allocation of the `BytesMut`.
824+
///
825+
/// If the `BytesMut` is empty but the underlying storage has been used before,
826+
/// it might be possible to cheaply reclaim space by just updating a few indices.
827+
/// Returns `None` if the `BytesMut` is not empty, there is nothing to reclaim
828+
/// (no underlying storage has been allocated), or there are any other live
829+
/// references to the underlying storage. Otherwise, returns the available
830+
/// capacity after reclaiming.
831+
///
832+
/// # Examples
833+
///
834+
/// ```
835+
/// use bytes::BytesMut;
836+
/// use core::num::NonZeroUsize;
837+
///
838+
/// let mut buf = BytesMut::with_capacity(64);
839+
/// assert_eq!(None, buf.try_reclaim());
840+
///
841+
/// buf.extend_from_slice(b"abcd");
842+
/// let mut split = buf.split();
843+
/// assert_eq!(None, split.try_reclaim());
844+
/// assert_eq!(None, buf.try_reclaim());
845+
/// drop(buf);
846+
/// assert_eq!(None, split.try_reclaim());
847+
/// split.clear();
848+
/// assert_eq!(Some(64), split.try_reclaim().map(NonZeroUsize::into));
849+
/// ```
850+
pub fn try_reclaim(&mut self) -> Option<NonZeroUsize> {
851+
if !self.is_empty() {
852+
return None
853+
}
854+
855+
let kind = self.kind();
856+
if kind == KIND_VEC {
857+
unsafe {
858+
let off = self.get_vec_pos();
859+
if off == 0 {
860+
return None
861+
}
862+
863+
let base_ptr = self.ptr.as_ptr().sub(off);
864+
self.ptr = vptr(base_ptr);
865+
self.set_vec_pos(0);
866+
self.cap += off;
867+
debug_assert!(self.capacity() > 0);
868+
return Some(NonZeroUsize::new_unchecked(self.capacity()))
869+
}
870+
}
871+
let shared: *mut Shared = self.data;
872+
873+
unsafe {
874+
if !(*shared).is_unique() {
875+
return None
876+
}
877+
let v = &mut (*shared).vec;
878+
879+
let ptr = v.as_mut_ptr();
880+
self.ptr = vptr(ptr);
881+
self.cap = v.capacity();
882+
debug_assert!(self.capacity() > 0);
883+
Some(NonZeroUsize::new_unchecked(self.capacity()))
884+
}
885+
}
886+
822887
// private
823888

824889
// For now, use a `Vec` to manage the memory for us, but we may want to

tests/test_bytes.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![warn(rust_2018_idioms)]
22

3+
use std::num::NonZeroUsize;
34
use bytes::{Buf, BufMut, Bytes, BytesMut};
45

56
use std::usize;
@@ -1172,3 +1173,43 @@ fn shared_is_unique() {
11721173
drop(b);
11731174
assert!(c.is_unique());
11741175
}
1176+
1177+
#[test]
1178+
fn try_reclaim_empty() {
1179+
let mut buf = BytesMut::new();
1180+
assert_eq!(None, buf.try_reclaim());
1181+
buf.reserve(6);
1182+
assert_eq!(None, buf.try_reclaim());
1183+
}
1184+
1185+
#[test]
1186+
fn try_reclaim_vec() {
1187+
let mut buf = BytesMut::with_capacity(6);
1188+
buf.put_slice(b"abc");
1189+
buf.advance(3);
1190+
assert_eq!(3, buf.capacity());
1191+
assert_eq!(Some(NonZeroUsize::new(6).unwrap()), buf.try_reclaim());
1192+
assert_eq!(6, buf.capacity());
1193+
}
1194+
1195+
#[test]
1196+
fn try_reclaim_arc() {
1197+
let mut buf = BytesMut::with_capacity(6);
1198+
buf.put_slice(b"abc");
1199+
let x = buf.split().freeze();
1200+
buf.put_slice(b"def");
1201+
let y = buf.split().freeze();
1202+
let z = y.clone();
1203+
assert_eq!(None, buf.try_reclaim());
1204+
drop(x);
1205+
drop(z);
1206+
assert_eq!(None, buf.try_reclaim());
1207+
drop(y);
1208+
assert_eq!(Some(NonZeroUsize::new(6).unwrap()), buf.try_reclaim());
1209+
assert_eq!(6, buf.capacity());
1210+
assert_eq!(0, buf.len());
1211+
buf.put_slice(b"abc");
1212+
buf.put_slice(b"def");
1213+
assert_eq!(6, buf.capacity());
1214+
assert_eq!(6, buf.len());
1215+
}

0 commit comments

Comments
 (0)