From fc2c02d3a19154f15827039006fb72a97877e456 Mon Sep 17 00:00:00 2001 From: Travis Sharp <2798069+tsharp@users.noreply.github.com> Date: Sat, 27 Jun 2026 11:00:39 -0700 Subject: [PATCH 1/4] fix(bytes): make BytesMut::try_shrink_to_fit safe for offset views --- src/bytes_mut.rs | 54 ++++++++++++++++++++++++++++++++++++++++++ tests/test_buf_mut.rs | 55 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 689f4b478..7f042069a 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -858,6 +858,60 @@ impl BytesMut { self.reserve_inner(additional, false) } + /// Attempts to shrink the buffer's capacity to fit its current length. + /// + /// This operation preserves the current contents and returns `true` when + /// the allocation is successfully shrunk. For `BytesMut` values backed by + /// shared storage, this only succeeds when the current handle is the only + /// remaining owner of the underlying allocation; otherwise it returns + /// `false` and leaves the buffer unchanged. + /// + /// # Examples + /// + /// ``` + /// use bytes::BytesMut; + /// + /// let mut buf = BytesMut::with_capacity(64); + /// buf.extend_from_slice(b"hello"); + /// + /// assert!(buf.try_shrink_to_fit()); + /// assert_eq!(buf.capacity(), 5); + /// assert_eq!(&buf[..], b"hello"); + /// ``` + pub fn try_shrink_to_fit(&mut self) -> bool { + let kind = self.kind(); + + if kind == KIND_VEC { + unsafe { + let off = self.get_vec_pos(); + let v = rebuild_vec(self.ptr.as_ptr(), self.len, self.cap, off); + let mut v = ManuallyDrop::new(v); + v.shrink_to_fit(); + self.ptr = vptr(v.as_mut_ptr().add(off)); + self.cap = v.capacity() - off; + } + return true; + } + + debug_assert_eq!(kind, KIND_ARC); + let shared: *mut Shared = self.data; + + unsafe { + if (*shared).is_unique() { + let v = &mut (*shared).vec; + let v_ptr = v.as_mut_ptr(); + let offset = self.ptr.as_ptr().offset_from(v_ptr) as usize; + + v.shrink_to_fit(); + self.ptr = vptr(v.as_mut_ptr().add(offset)); + self.cap = v.capacity() - offset; + return true; + } + } + + false + } + /// Appends given bytes to this `BytesMut`. /// /// If this `BytesMut` object does not have enough capacity, it is resized diff --git a/tests/test_buf_mut.rs b/tests/test_buf_mut.rs index f1bd3b0a0..76fa2a0aa 100644 --- a/tests/test_buf_mut.rs +++ b/tests/test_buf_mut.rs @@ -282,3 +282,58 @@ fn test_bytes_mut_reuse() { let mut buf = BytesMut::new(); buf.put(&[1u8, 2, 3] as &[u8]); } + +#[test] +fn test_bytes_mut_try_shrink_to_fit_empty() { + let mut buf = BytesMut::new(); + + assert!(buf.try_shrink_to_fit()); + assert!(buf.is_empty()); + assert_eq!(buf.capacity(), 0); +} + +#[test] +fn test_bytes_mut_try_shrink_to_fit_already_tight() { + let mut buf = BytesMut::from(&b"abc"[..]); + + assert!(buf.try_shrink_to_fit()); + assert_eq!(buf.capacity(), buf.len()); + assert_eq!(&buf[..], b"abc"); +} + +#[test] +fn test_bytes_mut_try_shrink_to_fit_cant_shrink_with_refs() { + let mut buf = BytesMut::from(&b"abc"[..]); + buf.reserve(3); + + let mut other = buf.split_off(2); + + assert_eq!(buf.try_shrink_to_fit(), false, "if there are other references to the buffer, it can't be safely shrunk"); + assert_eq!(buf.capacity(), 2, "the capacity of the original buffer shrinks to the length of the buffer when there are other references to it"); + assert_eq!(&buf[..], b"ab", "the original buffer should still contain the first two bytes"); + + assert_eq!(other.try_shrink_to_fit(), false, "the other buffer can be shrunk to fit"); + assert_eq!(other.capacity(), 6, "the other buffer's capacity should be 6"); + assert_eq!(other.len(), 1, "the other buffer's length should be 1"); + assert_eq!(&other[..], b"c", "the other buffer should contain the last byte"); +} + +#[test] +fn test_bytes_mut_try_shrink_to_fit_after_split_off() { + let data = b"hello world"; + let mut buf = BytesMut::with_capacity(64); + + assert_eq!(buf.capacity(), 64, "buf capacity should be 64 before extending"); + buf.extend_from_slice(data); + assert_eq!(buf.capacity(), 64, "buf capacity should be 64 after extending"); + + let mut other = buf.split_off(5); + assert_eq!(&buf[..], b"hello", "buf should contain the first half of the data"); + assert_eq!(&other[..], b" world", "other should contain the second half of the data"); + assert_eq!(buf.capacity(), 5, "buf capacity should be 5 after splitting"); + assert_eq!(other.capacity(), 59, "other capacity should be 59 before shrinking"); + + drop(buf); + assert!(other.try_shrink_to_fit()); + assert_eq!(other.capacity(), 6, "other capacity should be 6 after shrinking"); +} \ No newline at end of file From cc16909448aefcc8d9ab0e0ef53b3f5d096d17d1 Mon Sep 17 00:00:00 2001 From: Travis Sharp <2798069+tsharp@users.noreply.github.com> Date: Sat, 27 Jun 2026 11:20:22 -0700 Subject: [PATCH 2/4] test(try_shrink_to_fit): additional testing around try_shrink_to_fit to validate behavior --- src/bytes_mut.rs | 23 ++++++++--- tests/test_buf_mut.rs | 95 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 96 insertions(+), 22 deletions(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 7f042069a..788488792 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -886,9 +886,16 @@ impl BytesMut { let off = self.get_vec_pos(); let v = rebuild_vec(self.ptr.as_ptr(), self.len, self.cap, off); let mut v = ManuallyDrop::new(v); + // Offset views may point past bytes that are not part of this + // buffer. Move the live range to the start before shrinking. + if self.len != 0 { + ptr::copy(self.ptr.as_ptr(), v.as_mut_ptr(), self.len); + } + v.set_len(self.len); v.shrink_to_fit(); - self.ptr = vptr(v.as_mut_ptr().add(off)); - self.cap = v.capacity() - off; + self.ptr = vptr(v.as_mut_ptr()); + self.cap = v.capacity(); + self.set_vec_pos(0); } return true; } @@ -899,12 +906,16 @@ impl BytesMut { unsafe { if (*shared).is_unique() { let v = &mut (*shared).vec; - let v_ptr = v.as_mut_ptr(); - let offset = self.ptr.as_ptr().offset_from(v_ptr) as usize; + // Offset views may point past bytes that are not part of this + // buffer. Move the live range to the start before shrinking. + if self.len != 0 { + ptr::copy(self.ptr.as_ptr(), v.as_mut_ptr(), self.len); + } + v.set_len(self.len); v.shrink_to_fit(); - self.ptr = vptr(v.as_mut_ptr().add(offset)); - self.cap = v.capacity() - offset; + self.ptr = vptr(v.as_mut_ptr()); + self.cap = v.capacity(); return true; } } diff --git a/tests/test_buf_mut.rs b/tests/test_buf_mut.rs index 76fa2a0aa..dbda1c24f 100644 --- a/tests/test_buf_mut.rs +++ b/tests/test_buf_mut.rs @@ -1,7 +1,7 @@ #![warn(rust_2018_idioms)] use bytes::buf::UninitSlice; -use bytes::{BufMut, BytesMut}; +use bytes::{Buf, BufMut, BytesMut}; use core::fmt::Write; use core::mem::MaybeUninit; @@ -308,14 +308,14 @@ fn test_bytes_mut_try_shrink_to_fit_cant_shrink_with_refs() { let mut other = buf.split_off(2); - assert_eq!(buf.try_shrink_to_fit(), false, "if there are other references to the buffer, it can't be safely shrunk"); - assert_eq!(buf.capacity(), 2, "the capacity of the original buffer shrinks to the length of the buffer when there are other references to it"); - assert_eq!(&buf[..], b"ab", "the original buffer should still contain the first two bytes"); + assert!(!buf.try_shrink_to_fit()); + assert_eq!(buf.capacity(), 2); + assert_eq!(&buf[..], b"ab"); - assert_eq!(other.try_shrink_to_fit(), false, "the other buffer can be shrunk to fit"); - assert_eq!(other.capacity(), 6, "the other buffer's capacity should be 6"); - assert_eq!(other.len(), 1, "the other buffer's length should be 1"); - assert_eq!(&other[..], b"c", "the other buffer should contain the last byte"); + assert!(!other.try_shrink_to_fit()); + assert_eq!(other.capacity(), 6); + assert_eq!(other.len(), 1); + assert_eq!(&other[..], b"c"); } #[test] @@ -323,17 +323,80 @@ fn test_bytes_mut_try_shrink_to_fit_after_split_off() { let data = b"hello world"; let mut buf = BytesMut::with_capacity(64); - assert_eq!(buf.capacity(), 64, "buf capacity should be 64 before extending"); + assert_eq!(buf.capacity(), 64); buf.extend_from_slice(data); - assert_eq!(buf.capacity(), 64, "buf capacity should be 64 after extending"); + assert_eq!(buf.capacity(), 64); let mut other = buf.split_off(5); - assert_eq!(&buf[..], b"hello", "buf should contain the first half of the data"); - assert_eq!(&other[..], b" world", "other should contain the second half of the data"); - assert_eq!(buf.capacity(), 5, "buf capacity should be 5 after splitting"); - assert_eq!(other.capacity(), 59, "other capacity should be 59 before shrinking"); + assert_eq!(&buf[..], b"hello"); + assert_eq!(&other[..], b" world"); + assert_eq!(buf.capacity(), 5); + assert_eq!(other.capacity(), 59); + + drop(buf); + assert!(other.try_shrink_to_fit()); + assert_eq!(other.capacity(), 6); +} + +#[test] +fn test_bytes_mut_try_shrink_to_fit_unique_front_after_split_off() { + let mut buf = BytesMut::with_capacity(64); + buf.extend_from_slice(b"hello world"); + + let other = buf.split_off(5); + drop(other); + assert!(buf.try_shrink_to_fit()); + assert_eq!(buf.capacity(), buf.len()); + assert_eq!(buf.capacity(), 5); + assert_eq!(&buf[..], b"hello"); +} + +#[test] +fn test_bytes_mut_try_shrink_to_fit_unique_empty_tail_past_len() { + let mut buf = BytesMut::with_capacity(64); + buf.extend_from_slice(b"abc"); + + let mut other = buf.split_off(10); drop(buf); + assert!(other.try_shrink_to_fit()); - assert_eq!(other.capacity(), 6, "other capacity should be 6 after shrinking"); -} \ No newline at end of file + assert!(other.is_empty()); + assert_eq!(other.capacity(), 0); +} + +#[test] +fn test_bytes_mut_try_shrink_to_fit_after_advance() { + let mut buf = BytesMut::with_capacity(64); + buf.extend_from_slice(b"hello world"); + buf.advance(6); + + assert!(buf.try_shrink_to_fit()); + assert_eq!(buf.capacity(), buf.len()); + assert_eq!(buf.capacity(), 5); + assert_eq!(&buf[..], b"world"); +} + +#[test] +fn test_bytes_mut_try_shrink_to_fit_empty_after_advance() { + let mut buf = BytesMut::with_capacity(64); + buf.extend_from_slice(b"abc"); + buf.advance(3); + + assert!(buf.try_shrink_to_fit()); + assert!(buf.is_empty()); + assert_eq!(buf.capacity(), 0); +} + +#[test] +fn test_bytes_mut_try_shrink_to_fit_cant_shrink_with_bytes_ref() { + let mut buf = BytesMut::from(&b"abcdef"[..]); + buf.reserve(8); + + let bytes = buf.split_off(3).freeze(); + + assert!(!buf.try_shrink_to_fit()); + assert_eq!(buf.capacity(), 3); + assert_eq!(&buf[..], b"abc"); + assert_eq!(&bytes[..], b"def"); +} From 9d7e5437bcc47e4e5363a27b9f3b8627fe3e5019 Mon Sep 17 00:00:00 2001 From: Travis Sharp <2798069+tsharp@users.noreply.github.com> Date: Sat, 27 Jun 2026 11:23:39 -0700 Subject: [PATCH 3/4] cleanup / polish try_shrink_to_fit --- src/bytes_mut.rs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index 788488792..b4c09b2f4 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -886,13 +886,7 @@ impl BytesMut { let off = self.get_vec_pos(); let v = rebuild_vec(self.ptr.as_ptr(), self.len, self.cap, off); let mut v = ManuallyDrop::new(v); - // Offset views may point past bytes that are not part of this - // buffer. Move the live range to the start before shrinking. - if self.len != 0 { - ptr::copy(self.ptr.as_ptr(), v.as_mut_ptr(), self.len); - } - v.set_len(self.len); - v.shrink_to_fit(); + shrink_vec_to_fit(&mut v, self.ptr.as_ptr(), self.len); self.ptr = vptr(v.as_mut_ptr()); self.cap = v.capacity(); self.set_vec_pos(0); @@ -907,13 +901,7 @@ impl BytesMut { if (*shared).is_unique() { let v = &mut (*shared).vec; - // Offset views may point past bytes that are not part of this - // buffer. Move the live range to the start before shrinking. - if self.len != 0 { - ptr::copy(self.ptr.as_ptr(), v.as_mut_ptr(), self.len); - } - v.set_len(self.len); - v.shrink_to_fit(); + shrink_vec_to_fit(v, self.ptr.as_ptr(), self.len); self.ptr = vptr(v.as_mut_ptr()); self.cap = v.capacity(); return true; @@ -1933,6 +1921,17 @@ unsafe fn rebuild_vec(ptr: *mut u8, mut len: usize, mut cap: usize, off: usize) Vec::from_raw_parts(ptr, len, cap) } +// `ptr..ptr + len` must be initialized bytes within `vec`'s allocation. +unsafe fn shrink_vec_to_fit(vec: &mut Vec, ptr: *mut u8, len: usize) { + // Offset views may point past bytes that are not part of this buffer. Move + // the live range to the start before shrinking. + if len != 0 { + ptr::copy(ptr, vec.as_mut_ptr(), len); + } + vec.set_len(len); + vec.shrink_to_fit(); +} + // ===== impl SharedVtable ===== static SHARED_VTABLE: Vtable = Vtable { From b055fa99a063b0b542137c32e29fa3ad9c1ed32e Mon Sep 17 00:00:00 2001 From: Travis Sharp <2798069+tsharp@users.noreply.github.com> Date: Sat, 27 Jun 2026 11:37:45 -0700 Subject: [PATCH 4/4] feat(bytes): add BytesMut::try_shrink_to_capacity --- src/bytes_mut.rs | 51 ++++++++++++++++++++++++++++++++----------- tests/test_buf_mut.rs | 50 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 13 deletions(-) diff --git a/src/bytes_mut.rs b/src/bytes_mut.rs index b4c09b2f4..87efa5816 100644 --- a/src/bytes_mut.rs +++ b/src/bytes_mut.rs @@ -858,13 +858,14 @@ impl BytesMut { self.reserve_inner(additional, false) } - /// Attempts to shrink the buffer's capacity to fit its current length. + /// Attempts to shrink the buffer's capacity with a lower bound. /// - /// This operation preserves the current contents and returns `true` when - /// the allocation is successfully shrunk. For `BytesMut` values backed by - /// shared storage, this only succeeds when the current handle is the only - /// remaining owner of the underlying allocation; otherwise it returns - /// `false` and leaves the buffer unchanged. + /// The capacity will remain at least as large as both the length and the + /// supplied capacity. This operation preserves the current contents and + /// returns `true` when the allocation can be reclaimed. For `BytesMut` + /// values backed by shared storage, this only succeeds when the current + /// handle is the only remaining owner of the underlying allocation; + /// otherwise it returns `false` and leaves the buffer unchanged. /// /// # Examples /// @@ -874,11 +875,11 @@ impl BytesMut { /// let mut buf = BytesMut::with_capacity(64); /// buf.extend_from_slice(b"hello"); /// - /// assert!(buf.try_shrink_to_fit()); - /// assert_eq!(buf.capacity(), 5); + /// assert!(buf.try_shrink_to_capacity(16)); + /// assert_eq!(buf.capacity(), 16); /// assert_eq!(&buf[..], b"hello"); /// ``` - pub fn try_shrink_to_fit(&mut self) -> bool { + pub fn try_shrink_to_capacity(&mut self, capacity: usize) -> bool { let kind = self.kind(); if kind == KIND_VEC { @@ -886,7 +887,7 @@ impl BytesMut { let off = self.get_vec_pos(); let v = rebuild_vec(self.ptr.as_ptr(), self.len, self.cap, off); let mut v = ManuallyDrop::new(v); - shrink_vec_to_fit(&mut v, self.ptr.as_ptr(), self.len); + shrink_vec(&mut v, self.ptr.as_ptr(), self.len, capacity); self.ptr = vptr(v.as_mut_ptr()); self.cap = v.capacity(); self.set_vec_pos(0); @@ -901,7 +902,7 @@ impl BytesMut { if (*shared).is_unique() { let v = &mut (*shared).vec; - shrink_vec_to_fit(v, self.ptr.as_ptr(), self.len); + shrink_vec(v, self.ptr.as_ptr(), self.len, capacity); self.ptr = vptr(v.as_mut_ptr()); self.cap = v.capacity(); return true; @@ -911,6 +912,30 @@ impl BytesMut { false } + /// Attempts to shrink the buffer's capacity to fit its current length. + /// + /// This operation preserves the current contents and returns `true` when + /// the allocation is successfully shrunk. For `BytesMut` values backed by + /// shared storage, this only succeeds when the current handle is the only + /// remaining owner of the underlying allocation; otherwise it returns + /// `false` and leaves the buffer unchanged. + /// + /// # Examples + /// + /// ``` + /// use bytes::BytesMut; + /// + /// let mut buf = BytesMut::with_capacity(64); + /// buf.extend_from_slice(b"hello"); + /// + /// assert!(buf.try_shrink_to_fit()); + /// assert_eq!(buf.capacity(), 5); + /// assert_eq!(&buf[..], b"hello"); + /// ``` + pub fn try_shrink_to_fit(&mut self) -> bool { + self.try_shrink_to_capacity(self.len) + } + /// Appends given bytes to this `BytesMut`. /// /// If this `BytesMut` object does not have enough capacity, it is resized @@ -1922,14 +1947,14 @@ unsafe fn rebuild_vec(ptr: *mut u8, mut len: usize, mut cap: usize, off: usize) } // `ptr..ptr + len` must be initialized bytes within `vec`'s allocation. -unsafe fn shrink_vec_to_fit(vec: &mut Vec, ptr: *mut u8, len: usize) { +unsafe fn shrink_vec(vec: &mut Vec, ptr: *mut u8, len: usize, capacity: usize) { // Offset views may point past bytes that are not part of this buffer. Move // the live range to the start before shrinking. if len != 0 { ptr::copy(ptr, vec.as_mut_ptr(), len); } vec.set_len(len); - vec.shrink_to_fit(); + vec.shrink_to(capacity); } // ===== impl SharedVtable ===== diff --git a/tests/test_buf_mut.rs b/tests/test_buf_mut.rs index dbda1c24f..71771a378 100644 --- a/tests/test_buf_mut.rs +++ b/tests/test_buf_mut.rs @@ -400,3 +400,53 @@ fn test_bytes_mut_try_shrink_to_fit_cant_shrink_with_bytes_ref() { assert_eq!(&buf[..], b"abc"); assert_eq!(&bytes[..], b"def"); } + +#[test] +fn test_bytes_mut_try_shrink_to_capacity() { + let mut buf = BytesMut::with_capacity(64); + buf.extend_from_slice(b"hello"); + + assert!(buf.try_shrink_to_capacity(16)); + assert_eq!(buf.capacity(), 16); + assert_eq!(&buf[..], b"hello"); +} + +#[test] +fn test_bytes_mut_try_shrink_to_capacity_does_not_shrink_below_len() { + let mut buf = BytesMut::with_capacity(64); + buf.extend_from_slice(b"hello world"); + + assert!(buf.try_shrink_to_capacity(5)); + assert_eq!(buf.capacity(), buf.len()); + assert_eq!(buf.capacity(), 11); + assert_eq!(&buf[..], b"hello world"); +} + +#[test] +fn test_bytes_mut_try_shrink_to_capacity_after_advance() { + let mut buf = BytesMut::with_capacity(64); + buf.extend_from_slice(b"hello world"); + buf.advance(6); + + assert!(buf.try_shrink_to_capacity(16)); + assert_eq!(buf.capacity(), 16); + assert_eq!(&buf[..], b"world"); +} + +#[test] +fn test_bytes_mut_try_shrink_to_capacity_cant_shrink_with_refs() { + let mut buf = BytesMut::from(&b"abcdef"[..]); + buf.reserve(8); + + let mut other = buf.split_off(3); + let buf_cap = buf.capacity(); + let other_cap = other.capacity(); + + assert!(!buf.try_shrink_to_capacity(4)); + assert_eq!(buf.capacity(), buf_cap); + assert_eq!(&buf[..], b"abc"); + + assert!(!other.try_shrink_to_capacity(4)); + assert_eq!(other.capacity(), other_cap); + assert_eq!(&other[..], b"def"); +}