Skip to content

Commit f4870fd

Browse files
authored
Rollup merge of #160563 - Ddystopia:improve-buffer-cursor, r=clarfonthey
Make `BorrowedCursor<'a, T>` covariant in `'a` and drop an indirection This is a solution to #117693 (comment), with some improvements. Currently `'a` in `BorrowedCursor` is invariant, though people seem to talk about it as if it were covariant, and the feature is in FCP right now. The previous version with `'buf` and `'data` lifetimes had the same flaw: `'data` was invariant. A later PR landed that merged them and said that `BorrowedCursor` manually ensures that `'data` won't be ever overwritten thus invariance should not be needed. But unfortunately the lifetime is still left invariant. You can see it here, and the error spells it out exactly: ```rust #![feature(core_io_borrowed_buf)] use std::io::{BorrowedBuf, BorrowedCursor}; // Accepted. fn buf_covariant<'short, 'long: 'short>(buf: BorrowedBuf<'long, u8>) -> BorrowedBuf<'short, u8> { buf } // Rejected. fn cursor_covariant<'short, 'long: 'short>( cursor: BorrowedCursor<'long, u8>, ) -> BorrowedCursor<'short, u8> { cursor } // Rejected. fn cursor_contravariant<'short, 'long: 'short>( cursor: BorrowedCursor<'short, u8>, ) -> BorrowedCursor<'long, u8> { cursor } fn main() {} ``` And the errors (also say that `BorrowedCursor` is invariant over `'a`): ``` error: lifetime may not live long enough --> src/main.rs:14:5 | 11 | fn cursor_covariant<'short, 'long: 'short>( | ------ ----- lifetime `'long` defined here | | | lifetime `'short` defined here ... 14 | cursor | ^^^^^^ function was supposed to return data with lifetime `'long` but it is returning data with lifetime `'short` | = help: consider adding the following bound: `'short: 'long` = note: requirement occurs because of the type `BorrowedCursor<'_, u8>`, which makes the generic argument `'_` invariant = note: the struct `BorrowedCursor<'a, T>` is invariant over the parameter `'a` = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance error: lifetime may not live long enough --> src/main.rs:21:5 | 18 | fn cursor_contravariant<'short, 'long: 'short>( | ------ ----- lifetime `'long` defined here | | | lifetime `'short` defined here ... 21 | cursor | ^^^^^^ function was supposed to return data with lifetime `'long` but it is returning data with lifetime `'short` | = help: consider adding the following bound: `'short: 'long` = note: requirement occurs because of the type `BorrowedCursor<'_, u8>`, which makes the generic argument `'_` invariant = note: the struct `BorrowedCursor<'a, T>` is invariant over the parameter `'a` = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance error: could not compile `play` (bin "play") due to 2 previous errors ``` This also removes the two `mem::transmute` calls that `unfilled` and `reborrow` used to shorten `&'this mut BorrowedBuf<'data, T>` into `&'this mut BorrowedBuf<'this, T>`. --- Additionally I noticed that `BorrowedCursor` is not really as efficient as it could be, for a standard library: it contained a reference to the `BorrowedBuf`, which in turn contains a slice to the data. Without this, the fix is just replacing `&'a mut BorrowedBuf<'a, T>` with `NonNull<BorrowedBuf<'a, T>>`, plus some convenience helpers. To fix this, I also stored a reborrowed pointer to the first element of the array, with the provenance to access the whole array. `filled` and `init` are still read from the pointer to `BorrowedBuf`, the buffer length is also read from it but carefully, in order to not create a retag which will trigger a foreign access to the pointer stored in `BorrowedCursor`, making it disabled. It increased the size of `BorrowedCursor` from one `usize` to two of them. It is stored as the pointer rather than `&mut [MaybeUninit<T>]` to save a `usize` from the `BorrowedCursor` size.
2 parents ca39ddb + c7de933 commit f4870fd

1 file changed

Lines changed: 136 additions & 48 deletions

File tree

library/core/src/io/borrowed_buf.rs

Lines changed: 136 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#![unstable(feature = "core_io_borrowed_buf", issue = "117693")]
22

33
use crate::fmt::{self, Debug, Formatter};
4-
use crate::mem::{self, MaybeUninit};
4+
use crate::mem::MaybeUninit;
5+
use crate::ptr::NonNull;
6+
use crate::slice;
57

68
/// A borrowed buffer of initially uninitialized elements, which is incrementally filled.
79
///
@@ -34,11 +36,23 @@ pub struct BorrowedBuf<'data, T> {
3436
}
3537

3638
impl<T> Debug for BorrowedBuf<'_, T> {
39+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
40+
BorrowedBufDebug { init: self.init, filled: self.filled, capacity: self.capacity() }.fmt(f)
41+
}
42+
}
43+
44+
struct BorrowedBufDebug {
45+
init: bool,
46+
filled: usize,
47+
capacity: usize,
48+
}
49+
50+
impl Debug for BorrowedBufDebug {
3751
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
3852
f.debug_struct("BorrowedBuf")
3953
.field("init", &self.init)
4054
.field("filled", &self.filled)
41-
.field("capacity", &self.capacity())
55+
.field("capacity", &self.capacity)
4256
.finish()
4357
}
4458
}
@@ -70,11 +84,15 @@ impl<'data, T: Copy> From<&'data mut [MaybeUninit<T>]> for BorrowedBuf<'data, T>
7084
impl<'data, T: Copy> From<BorrowedCursor<'data, T>> for BorrowedBuf<'data, T> {
7185
#[inline]
7286
fn from(buf: BorrowedCursor<'data, T>) -> BorrowedBuf<'data, T> {
87+
let filled = buf.filled();
88+
let init = buf.is_buf_init();
89+
let len = buf.buf_len();
7390
BorrowedBuf {
74-
// SAFETY: no initialized element is ever uninitialized as per `BorrowedBuf`'s invariant
75-
buf: unsafe { buf.buf.buf.get_unchecked_mut(buf.buf.filled..) },
91+
// SAFETY: no initialized element is ever uninitialized as per `BorrowedBuf`'s
92+
// invariant, and the cursor holds the unique access to those elements for `'data`
93+
buf: unsafe { slice::from_raw_parts_mut(buf.buf.as_ptr().add(filled), len - filled) },
7694
filled: 0,
77-
init: buf.buf.init,
95+
init,
7896
}
7997
}
8098
}
@@ -144,15 +162,8 @@ impl<'data, T: Copy> BorrowedBuf<'data, T> {
144162
/// Returns a cursor over the unfilled part of the buffer.
145163
#[inline]
146164
pub fn unfilled<'this>(&'this mut self) -> BorrowedCursor<'this, T> {
147-
BorrowedCursor {
148-
// SAFETY: we never assign into `BorrowedCursor::buf`, so treating its
149-
// lifetime covariantly is safe.
150-
buf: unsafe {
151-
mem::transmute::<&'this mut BorrowedBuf<'data, T>, &'this mut BorrowedBuf<'this, T>>(
152-
self,
153-
)
154-
},
155-
}
165+
let borrowed_buf = NonNull::from_mut(self);
166+
BorrowedCursor { buf: NonNull::from_mut(self.buf).cast(), borrowed_buf }
156167
}
157168

158169
/// Clears the buffer, resetting the filled region to empty.
@@ -193,16 +204,98 @@ impl<'data, T: Copy> BorrowedBuf<'data, T> {
193204
/// The lifetime `'a` is a bound on the lifetime of the underlying buffer (which means it is a bound
194205
/// on the elements in that buffer by transitivity).
195206
pub struct BorrowedCursor<'a, T> {
196-
/// The underlying buffer.
197-
// Safety invariant: we treat the type of buf as covariant in the lifetime of `BorrowedBuf` when
198-
// we create a `BorrowedCursor`. This is only safe if we never replace `buf` by assigning into
199-
// it, so don't do that!
200-
buf: &'a mut BorrowedBuf<'a, T>,
207+
/// The start of the elements of the buffer this cursor was created from.
208+
/// Safety invariant: this points to the start of the *whole* buffer of `*borrowed_buf` and is
209+
/// valid for reads and writes of `(*borrowed_buf).buf.len()` elements, so that
210+
/// `(*borrowed_buf).filled` indexes into it.
211+
buf: NonNull<MaybeUninit<T>>,
212+
/// The buffer this cursor was created from.
213+
/// Safety invariants:
214+
/// 1. `(*borrowed_buf).buf` is *never* accessed by the owner of the pointee while the `buf`
215+
/// field above is alive, because there is a `&mut` of the pointee while the cursor is alive.
216+
/// 2. We promise to only access the `filled` and `init` fields and the metadata of the `buf`
217+
/// field through the `borrowed_buf` pointer, never triggering any retag of `buf`'s pointer,
218+
/// as the `buf` field above holds a reborrow of it and reaching the parent again would be a
219+
/// foreign access for that reborrow. This includes not making a reference to the whole
220+
/// pointee out of `borrowed_buf`, but only accessing those fields directly through pointer
221+
/// manipulation.
222+
borrowed_buf: NonNull<BorrowedBuf<'a, T>>,
201223
}
202224

203225
impl<T> Debug for BorrowedCursor<'_, T> {
204226
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
205-
f.debug_struct("BorrowedCursor").field("buf", &self.buf).finish()
227+
let buf = BorrowedBufDebug {
228+
init: self.is_buf_init(),
229+
filled: self.filled(),
230+
capacity: self.buf_len(),
231+
};
232+
233+
f.debug_struct("BorrowedCursor").field("buf", &buf).finish()
234+
}
235+
}
236+
237+
// Helpers to access underlying buffer state.
238+
impl<'a, T> BorrowedCursor<'a, T> {
239+
#[inline]
240+
fn buf_mut(&mut self) -> &mut [MaybeUninit<T>] {
241+
let len = self.buf_len();
242+
// SAFETY: `buf` points to `len` elements that this cursor borrows exclusively.
243+
unsafe { slice::from_raw_parts_mut(self.buf.as_ptr(), len) }
244+
}
245+
246+
#[inline]
247+
fn buf_len(&self) -> usize {
248+
// SAFETY: We read just the metadata of `buf` and avoid retagging the reference.
249+
unsafe {
250+
let borrowed_buf = self.borrowed_buf.as_ptr();
251+
let buf_ptr: *const &'a mut [MaybeUninit<T>] = &raw const (*borrowed_buf).buf;
252+
// Same layout:
253+
// https://doc.rust-lang.org/reference/type-layout.html#r-layout.pointer.intro
254+
let buf_ptr: *const *const [MaybeUninit<T>] = buf_ptr.cast();
255+
let buf: *const [MaybeUninit<T>] = *buf_ptr;
256+
buf.len()
257+
}
258+
}
259+
260+
#[inline]
261+
fn unfilled_slice(&mut self) -> &mut [MaybeUninit<T>] {
262+
let filled = self.filled();
263+
// SAFETY: always in bounds
264+
unsafe { self.buf_mut().get_unchecked_mut(filled..) }
265+
}
266+
267+
#[inline]
268+
fn filled(&self) -> usize {
269+
// SAFETY: We access just `filled` and avoid foreign read on `buf`.
270+
unsafe { (*self.borrowed_buf.as_ptr()).filled }
271+
}
272+
273+
#[inline]
274+
fn is_buf_init(&self) -> bool {
275+
// SAFETY: We access just `init` and avoid foreign read on `buf`.
276+
unsafe { (*self.borrowed_buf.as_ptr()).init }
277+
}
278+
279+
/// # Safety
280+
///
281+
/// In case of `true` all the elements of the cursor must be initialized.
282+
#[inline]
283+
unsafe fn set_buf_init(&mut self, init: bool) {
284+
// SAFETY: We access just `init` and avoid foreign read on `buf`.
285+
unsafe {
286+
(*self.borrowed_buf.as_ptr()).init = init;
287+
}
288+
}
289+
290+
/// # Safety
291+
///
292+
/// The next `n` elements of the cursor must be initialized.
293+
#[inline]
294+
unsafe fn add_filled(&mut self, n: usize) {
295+
// SAFETY: We access just `filled` and avoid foreign read on `buf`.
296+
unsafe {
297+
(*self.borrowed_buf.as_ptr()).filled += n;
298+
}
206299
}
207300
}
208301

@@ -213,36 +306,28 @@ impl<'a, T: Copy> BorrowedCursor<'a, T> {
213306
/// not accessible while the new cursor exists.
214307
#[inline]
215308
pub fn reborrow<'this>(&'this mut self) -> BorrowedCursor<'this, T> {
216-
BorrowedCursor {
217-
// SAFETY: we never assign into `BorrowedCursor::buf`, so treating its
218-
// lifetime covariantly is safe.
219-
buf: unsafe {
220-
mem::transmute::<&'this mut BorrowedBuf<'a, T>, &'this mut BorrowedBuf<'this, T>>(
221-
self.buf,
222-
)
223-
},
224-
}
309+
BorrowedCursor { buf: self.buf, borrowed_buf: self.borrowed_buf }
225310
}
226311

227312
/// Returns the available space in the cursor.
228313
#[inline]
229314
pub fn capacity(&self) -> usize {
230-
self.buf.capacity() - self.buf.filled
315+
self.buf_len() - self.filled()
231316
}
232317

233318
/// Returns the number of elements written to the `BorrowedBuf` this cursor was created from.
234319
///
235320
/// In particular, the count returned is shared by all reborrows of the cursor.
236321
#[inline]
237322
pub fn written(&self) -> usize {
238-
self.buf.filled
323+
self.filled()
239324
}
240325

241326
/// Returns `true` if the buffer is initialized.
242327
#[unstable(feature = "borrowed_buf_init", issue = "160476")]
243328
#[inline]
244329
pub fn is_init(&self) -> bool {
245-
self.buf.init
330+
self.is_buf_init()
246331
}
247332

248333
/// Set the buffer as fully initialized.
@@ -253,7 +338,8 @@ impl<'a, T: Copy> BorrowedCursor<'a, T> {
253338
#[unstable(feature = "borrowed_buf_init", issue = "160476")]
254339
#[inline]
255340
pub unsafe fn set_init(&mut self) {
256-
self.buf.init = true;
341+
// SAFETY: the caller guarantees that all the elements of the cursor are initialized.
342+
unsafe { self.set_buf_init(true) }
257343
}
258344

259345
/// Returns a mutable reference to the whole cursor.
@@ -263,8 +349,7 @@ impl<'a, T: Copy> BorrowedCursor<'a, T> {
263349
/// The caller must not uninitialize any elements of the cursor if it is initialized.
264350
#[inline]
265351
pub unsafe fn as_mut(&mut self) -> &mut [MaybeUninit<T>] {
266-
// SAFETY: always in bounds
267-
unsafe { self.buf.buf.get_unchecked_mut(self.buf.filled..) }
352+
self.unfilled_slice()
268353
}
269354

270355
/// Advances the cursor by asserting that `n` elements have been filled.
@@ -283,10 +368,11 @@ impl<'a, T: Copy> BorrowedCursor<'a, T> {
283368
#[inline]
284369
pub fn advance_checked(&mut self, n: usize) -> &mut Self {
285370
// The subtraction cannot underflow by invariant of this type.
286-
let init_unfilled = if self.buf.init { self.buf.buf.len() - self.buf.filled } else { 0 };
371+
let init_unfilled = if self.is_buf_init() { self.buf_len() - self.filled() } else { 0 };
287372
assert!(n <= init_unfilled);
288373

289-
self.buf.filled += n;
374+
// SAFETY: the next `n` elements are initialized, as asserted above.
375+
unsafe { self.advance(n) };
290376
self
291377
}
292378

@@ -301,7 +387,8 @@ impl<'a, T: Copy> BorrowedCursor<'a, T> {
301387
/// The caller must ensure that the first `n` elements of the cursor have been initialized.
302388
#[inline]
303389
pub unsafe fn advance(&mut self, n: usize) -> &mut Self {
304-
self.buf.filled += n;
390+
// SAFETY: the caller guarantees that the first `n` elements of the cursor are initialized.
391+
unsafe { self.add_filled(n) };
305392
self
306393
}
307394

@@ -319,7 +406,8 @@ impl<'a, T: Copy> BorrowedCursor<'a, T> {
319406
self.as_mut()[..buf.len()].write_copy_of_slice(buf);
320407
}
321408

322-
self.buf.filled += buf.len();
409+
// SAFETY: these elements have just been initialized.
410+
unsafe { self.advance(buf.len()) };
323411
}
324412

325413
/// Runs the given closure with a `BorrowedBuf` containing the unfilled part
@@ -349,8 +437,10 @@ impl<'a, T: Copy> BorrowedCursor<'a, T> {
349437
//
350438
// SAFETY: These elements were initialized/filled in the `BorrowedBuf`, and therefore they
351439
// are initialized/filled in the cursor too, because the buffer wasn't replaced.
352-
self.buf.init = init;
353-
self.buf.filled += filled;
440+
unsafe {
441+
self.set_buf_init(init);
442+
self.advance(filled);
443+
}
354444

355445
res
356446
}
@@ -362,15 +452,13 @@ impl<'a, T: Default + Copy> BorrowedCursor<'a, T> {
362452
#[unstable(feature = "borrowed_buf_init", issue = "160476")]
363453
#[inline]
364454
pub fn ensure_init(&mut self) -> &mut [T] {
365-
// SAFETY: always in bounds and we never uninitialize these elements.
366-
let unfilled = unsafe { self.buf.buf.get_unchecked_mut(self.buf.filled..) };
367-
368-
if !self.buf.init {
369-
unfilled.write_default();
370-
self.buf.init = true;
455+
if !self.is_buf_init() {
456+
self.unfilled_slice().write_default();
457+
// SAFETY: buf is now initialized.
458+
unsafe { self.set_buf_init(true) };
371459
}
372460

373461
// SAFETY: these elements have just been initialized if they weren't before
374-
unsafe { unfilled.assume_init_mut() }
462+
unsafe { self.unfilled_slice().assume_init_mut() }
375463
}
376464
}

0 commit comments

Comments
 (0)