-
-
Notifications
You must be signed in to change notification settings - Fork 14.1k
Make BorrowedBuf and BorrowedCursor generic over the data
#149749
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
joshtriplett
wants to merge
1
commit into
rust-lang:main
Choose a base branch
from
joshtriplett:borrowed-buf-generic
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+42
−35
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,11 +3,11 @@ | |
| use crate::fmt::{self, Debug, Formatter}; | ||
| use crate::mem::{self, MaybeUninit}; | ||
|
|
||
| /// A borrowed buffer of initially uninitialized bytes, which is incrementally filled. | ||
| /// A borrowed buffer of initially uninitialized data, which is incrementally filled. | ||
| /// | ||
| /// This type makes it safer to work with `MaybeUninit` buffers, such as to read into a buffer | ||
| /// without having to initialize it first. It tracks the region of bytes that have been filled and | ||
| /// the region that remains uninitialized. | ||
| /// without having to initialize it first. It tracks the region of data that has been filled and the | ||
| /// region that remains uninitialized. | ||
| /// | ||
| /// The contents of the buffer can be visualized as: | ||
| /// ```not_rust | ||
|
|
@@ -24,14 +24,16 @@ use crate::mem::{self, MaybeUninit}; | |
| /// `BorrowedCursor`. The cursor has write-only access to the unfilled portion of the buffer. | ||
| /// | ||
| /// The lifetime `'data` is a bound on the lifetime of the underlying data. | ||
| pub struct BorrowedBuf<'data> { | ||
| /// | ||
| /// The type defaults to managing bytes, but can manage any type of data. | ||
| pub struct BorrowedBuf<'data, T = u8> { | ||
| /// The buffer's underlying data. | ||
| buf: &'data mut [MaybeUninit<u8>], | ||
| /// The length of `self.buf` which is known to be filled. | ||
| buf: &'data mut [MaybeUninit<T>], | ||
| /// The number of elements of `self.buf` that are known to be filled. | ||
| filled: usize, | ||
| } | ||
|
|
||
| impl Debug for BorrowedBuf<'_> { | ||
| impl<T> Debug for BorrowedBuf<'_, T> { | ||
| fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { | ||
| f.debug_struct("BorrowedBuf") | ||
| .field("filled", &self.filled) | ||
|
|
@@ -41,13 +43,13 @@ impl Debug for BorrowedBuf<'_> { | |
| } | ||
|
|
||
| /// Creates a new `BorrowedBuf` from a fully initialized slice. | ||
| impl<'data> From<&'data mut [u8]> for BorrowedBuf<'data> { | ||
| impl<'data, T> From<&'data mut [T]> for BorrowedBuf<'data, T> { | ||
| #[inline] | ||
| fn from(slice: &'data mut [u8]) -> BorrowedBuf<'data> { | ||
| fn from(slice: &'data mut [T]) -> BorrowedBuf<'data, T> { | ||
| BorrowedBuf { | ||
| // SAFETY: Always in bounds. We treat the buffer as uninitialized, even though it's | ||
| // already initialized. | ||
| buf: unsafe { (slice as *mut [u8]).as_uninit_slice_mut().unwrap() }, | ||
| buf: unsafe { (slice as *mut [T]).as_uninit_slice_mut().unwrap() }, | ||
| filled: 0, | ||
| } | ||
| } | ||
|
|
@@ -56,19 +58,19 @@ impl<'data> From<&'data mut [u8]> for BorrowedBuf<'data> { | |
| /// Creates a new `BorrowedBuf` from an uninitialized buffer. | ||
| /// | ||
| /// Use `set_filled` if part of the buffer is known to be already filled. | ||
| impl<'data> From<&'data mut [MaybeUninit<u8>]> for BorrowedBuf<'data> { | ||
| impl<'data, T> From<&'data mut [MaybeUninit<T>]> for BorrowedBuf<'data, T> { | ||
| #[inline] | ||
| fn from(buf: &'data mut [MaybeUninit<u8>]) -> BorrowedBuf<'data> { | ||
| fn from(buf: &'data mut [MaybeUninit<T>]) -> BorrowedBuf<'data, T> { | ||
| BorrowedBuf { buf, filled: 0 } | ||
| } | ||
| } | ||
|
|
||
| /// Creates a new `BorrowedBuf` from a cursor. | ||
| /// | ||
| /// Use `BorrowedCursor::with_unfilled_buf` instead for a safer alternative. | ||
| impl<'data> From<BorrowedCursor<'data>> for BorrowedBuf<'data> { | ||
| impl<'data, T> From<BorrowedCursor<'data, T>> for BorrowedBuf<'data, T> { | ||
| #[inline] | ||
| fn from(buf: BorrowedCursor<'data>) -> BorrowedBuf<'data> { | ||
| fn from(buf: BorrowedCursor<'data, T>) -> BorrowedBuf<'data, T> { | ||
| BorrowedBuf { | ||
| // SAFETY: Always in bounds. We treat the buffer as uninitialized. | ||
| buf: unsafe { buf.buf.buf.get_unchecked_mut(buf.buf.filled..) }, | ||
|
|
@@ -77,7 +79,7 @@ impl<'data> From<BorrowedCursor<'data>> for BorrowedBuf<'data> { | |
| } | ||
| } | ||
|
|
||
| impl<'data> BorrowedBuf<'data> { | ||
| impl<'data, T> BorrowedBuf<'data, T> { | ||
| /// Returns the total capacity of the buffer. | ||
| #[inline] | ||
| pub fn capacity(&self) -> usize { | ||
|
|
@@ -92,7 +94,7 @@ impl<'data> BorrowedBuf<'data> { | |
|
|
||
| /// Returns a shared reference to the filled portion of the buffer. | ||
| #[inline] | ||
| pub fn filled(&self) -> &[u8] { | ||
| pub fn filled(&self) -> &[T] { | ||
| // SAFETY: We only slice the filled part of the buffer, which is always valid | ||
| unsafe { | ||
| let buf = self.buf.get_unchecked(..self.filled); | ||
|
|
@@ -102,7 +104,7 @@ impl<'data> BorrowedBuf<'data> { | |
|
|
||
| /// Returns a mutable reference to the filled portion of the buffer. | ||
| #[inline] | ||
| pub fn filled_mut(&mut self) -> &mut [u8] { | ||
| pub fn filled_mut(&mut self) -> &mut [T] { | ||
| // SAFETY: We only slice the filled part of the buffer, which is always valid | ||
| unsafe { | ||
| let buf = self.buf.get_unchecked_mut(..self.filled); | ||
|
|
@@ -112,7 +114,7 @@ impl<'data> BorrowedBuf<'data> { | |
|
|
||
| /// Returns a shared reference to the filled portion of the buffer with its original lifetime. | ||
| #[inline] | ||
| pub fn into_filled(self) -> &'data [u8] { | ||
| pub fn into_filled(self) -> &'data [T] { | ||
| // SAFETY: We only slice the filled part of the buffer, which is always valid | ||
| unsafe { | ||
| let buf = self.buf.get_unchecked(..self.filled); | ||
|
|
@@ -122,7 +124,7 @@ impl<'data> BorrowedBuf<'data> { | |
|
|
||
| /// Returns a mutable reference to the filled portion of the buffer with its original lifetime. | ||
| #[inline] | ||
| pub fn into_filled_mut(self) -> &'data mut [u8] { | ||
| pub fn into_filled_mut(self) -> &'data mut [T] { | ||
| // SAFETY: We only slice the filled part of the buffer, which is always valid | ||
| unsafe { | ||
| let buf = self.buf.get_unchecked_mut(..self.filled); | ||
|
|
@@ -132,12 +134,14 @@ impl<'data> BorrowedBuf<'data> { | |
|
|
||
| /// Returns a cursor over the unfilled part of the buffer. | ||
| #[inline] | ||
| pub fn unfilled<'this>(&'this mut self) -> BorrowedCursor<'this> { | ||
| pub fn unfilled<'this>(&'this mut self) -> BorrowedCursor<'this, T> { | ||
| BorrowedCursor { | ||
| // SAFETY: we never assign into `BorrowedCursor::buf`, so treating its | ||
| // lifetime covariantly is safe. | ||
| buf: unsafe { | ||
| mem::transmute::<&'this mut BorrowedBuf<'data>, &'this mut BorrowedBuf<'this>>(self) | ||
| mem::transmute::<&'this mut BorrowedBuf<'data, T>, &'this mut BorrowedBuf<'this, T>>( | ||
| self, | ||
| ) | ||
| }, | ||
| } | ||
| } | ||
|
|
@@ -157,7 +161,7 @@ impl<'data> BorrowedBuf<'data> { | |
| /// Data can be written directly to the cursor by using [`append`](BorrowedCursor::append) or | ||
| /// indirectly by getting a slice of part or all of the cursor and writing into the slice. In the | ||
| /// indirect case, the caller must call [`advance`](BorrowedCursor::advance) after writing to inform | ||
| /// the cursor how many bytes have been written. | ||
| /// the cursor how many elements have been written. | ||
| /// | ||
| /// Once data is written to the cursor, it becomes part of the filled portion of the underlying | ||
| /// `BorrowedBuf` and can no longer be accessed or re-written by the cursor. I.e., the cursor tracks | ||
|
|
@@ -166,26 +170,26 @@ impl<'data> BorrowedBuf<'data> { | |
| /// The lifetime `'a` is a bound on the lifetime of the underlying buffer (which means it is a bound | ||
| /// on the data in that buffer by transitivity). | ||
| #[derive(Debug)] | ||
| pub struct BorrowedCursor<'a> { | ||
| pub struct BorrowedCursor<'a, T = u8> { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. |
||
| /// The underlying buffer. | ||
| // Safety invariant: we treat the type of buf as covariant in the lifetime of `BorrowedBuf` when | ||
| // we create a `BorrowedCursor`. This is only safe if we never replace `buf` by assigning into | ||
| // it, so don't do that! | ||
| buf: &'a mut BorrowedBuf<'a>, | ||
| buf: &'a mut BorrowedBuf<'a, T>, | ||
| } | ||
|
|
||
| impl<'a> BorrowedCursor<'a> { | ||
| impl<'a, T> BorrowedCursor<'a, T> { | ||
| /// Reborrows this cursor by cloning it with a smaller lifetime. | ||
| /// | ||
| /// Since a cursor maintains unique access to its underlying buffer, the borrowed cursor is | ||
| /// not accessible while the new cursor exists. | ||
| #[inline] | ||
| pub fn reborrow<'this>(&'this mut self) -> BorrowedCursor<'this> { | ||
| pub fn reborrow<'this>(&'this mut self) -> BorrowedCursor<'this, T> { | ||
| BorrowedCursor { | ||
| // SAFETY: we never assign into `BorrowedCursor::buf`, so treating its | ||
| // lifetime covariantly is safe. | ||
| buf: unsafe { | ||
| mem::transmute::<&'this mut BorrowedBuf<'a>, &'this mut BorrowedBuf<'this>>( | ||
| mem::transmute::<&'this mut BorrowedBuf<'a, T>, &'this mut BorrowedBuf<'this, T>>( | ||
| self.buf, | ||
| ) | ||
| }, | ||
|
|
@@ -210,22 +214,22 @@ impl<'a> BorrowedCursor<'a> { | |
| /// | ||
| /// # Safety | ||
| /// | ||
| /// The caller must not uninitialize any previously initialized bytes. | ||
| /// The caller must not uninitialize any previously initialized data. | ||
| #[inline] | ||
| pub unsafe fn as_mut(&mut self) -> &mut [MaybeUninit<u8>] { | ||
| pub unsafe fn as_mut(&mut self) -> &mut [MaybeUninit<T>] { | ||
| // SAFETY: always in bounds | ||
| unsafe { self.buf.buf.get_unchecked_mut(self.buf.filled..) } | ||
| } | ||
|
|
||
| /// Advances the cursor by asserting that `n` bytes have been filled. | ||
| /// Advances the cursor by asserting that `n` elements have been filled. | ||
| /// | ||
| /// After advancing, the `n` bytes are no longer accessible via the cursor and can only be | ||
| /// After advancing, the `n` elements are no longer accessible via the cursor and can only be | ||
| /// accessed via the underlying buffer. I.e., the buffer's filled portion grows by `n` elements | ||
| /// and its unfilled portion (and the capacity of this cursor) shrinks by `n` elements. | ||
| /// | ||
| /// # Safety | ||
| /// | ||
| /// The caller must ensure that the first `n` bytes of the cursor have been initialized. `n` | ||
| /// The caller must ensure that the first `n` elements of the cursor have been initialized. `n` | ||
| /// must not exceed the remaining capacity of this cursor. | ||
| #[inline] | ||
| pub unsafe fn advance(&mut self, n: usize) -> &mut Self { | ||
|
|
@@ -239,7 +243,10 @@ impl<'a> BorrowedCursor<'a> { | |
| /// | ||
| /// Panics if `self.capacity()` is less than `buf.len()`. | ||
| #[inline] | ||
| pub fn append(&mut self, buf: &[u8]) { | ||
| pub fn append(&mut self, buf: &[T]) | ||
| where | ||
| T: Copy, | ||
| { | ||
| assert!(self.capacity() >= buf.len()); | ||
|
|
||
| // SAFETY: we do not de-initialize any of the elements of the slice | ||
|
|
@@ -259,7 +266,7 @@ impl<'a> BorrowedCursor<'a> { | |
| /// | ||
| /// Panics if the `BorrowedBuf` given to the closure is replaced by another | ||
| /// one. | ||
| pub fn with_unfilled_buf<T>(&mut self, f: impl FnOnce(&mut BorrowedBuf<'_>) -> T) -> T { | ||
| pub fn with_unfilled_buf<R>(&mut self, f: impl FnOnce(&mut BorrowedBuf<'_, T>) -> R) -> R { | ||
| let mut buf = BorrowedBuf::from(self.reborrow()); | ||
| let prev_ptr = buf.buf as *const _; | ||
| let res = f(&mut buf); | ||
|
|
@@ -269,7 +276,7 @@ impl<'a> BorrowedCursor<'a> { | |
| // there, one could mark some bytes as initialized even though there aren't. | ||
| assert!(core::ptr::addr_eq(prev_ptr, buf.buf)); | ||
|
|
||
| // SAFETY: These bytes were filled in the `BorrowedBuf`, so they're filled in the cursor | ||
| // SAFETY: These elements were filled in the `BorrowedBuf`, so they're filled in the cursor | ||
| // too, because the buffer wasn't replaced. | ||
| self.buf.filled += buf.filled; | ||
|
|
||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer not having u8 as a default. It's not a huge deal to have users write it out and we don't really have a precedent for hiding such things.