Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions library/core/src/slice/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ where
I: [const] SliceIndex<[T]>,
{
#[inline(always)]
#[rustc_no_writable]
fn index_mut(&mut self, index: I) -> &mut I::Output {
index.index_mut(self)
}
Expand Down Expand Up @@ -225,6 +226,7 @@ unsafe impl<T> const SliceIndex<[T]> for usize {
}

#[inline]
#[rustc_no_writable]

@RalfJung RalfJung Jun 12, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be nice if we could just add the attribute to the trait rather than each impl... but I am not sure how to implement that and it is not the usual behavior for such attributes.

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How exactly would that work? Just apply to the abstract method and then assume each implementation should have it applied?

That feels kind of unprecedented, since even attributes like #[inline] don't support that.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense because of the conceptual alignment between what SliceIndex is for and what the attribute is trying to express.

I think #[track_caller] can work like this, based on what I see in should_inherit_track_caller in the compiler.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, I had no idea you were able to use #[track_caller] in that way, and it turns out you're right: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=c726a3e5c00af55c75daaa67bf6533fc

Semantically, it makes sense, just, I had no idea that we had precedent for this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After catching up on the semantics of this attribute, at least from my understanding, the presence or absence of this attribute is always safe?

We don't really distinguish unsafe attributes (to my knowledge) for internal attributes, at least at the moment, but I guess the main issue with such a thing is if it would have noticeable effects that the method implementations would have to account for, e.g., such that those implementations would be best marked as unsafe or similar.

I guess this technically doesn't matter in this particular review, just trying to grasp a bit better how this would affect methods like this. I'm not sure if this necessarily changes any compiler semantics besides emitting an LLVM flag, e.g., if a future borrow checker would take this attribute into account.

@RalfJung RalfJung Jun 13, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The attribute affects whether code has UB, but on its own (i.e. in otherwise safe code) the presence or absence of the attribute can never cause UB. Furthermore, adding the attribute can never introduce UB to a program, but removing the attribute can introduce UB if surrounding unsafe code relied on the presence of the attribute. By default, our codegen backend entirely ignores the attribute; it takes a -Z flag (or Miri) for the attribute to do anything.

This is not meant to be taken into account by a borrow checker, just by LLVM and Miri. For now, it is just an experiment. If the experiment goes well, we could consider a more proper (e.g. type-based) solution.

fn get_mut(self, slice: &mut [T]) -> Option<&mut T> {
if self < slice.len() {
// SAFETY: `self` is checked to be in bounds.
Expand Down Expand Up @@ -273,6 +275,7 @@ unsafe impl<T> const SliceIndex<[T]> for usize {
}

#[inline]
#[rustc_no_writable]
fn index_mut(self, slice: &mut [T]) -> &mut T {
// N.B., use intrinsic indexing
&mut (*slice)[self]
Expand All @@ -296,6 +299,7 @@ unsafe impl<T> const SliceIndex<[T]> for ops::IndexRange {
}

#[inline]
#[rustc_no_writable]
fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> {
if self.end() <= slice.len() {
// SAFETY: `self` is checked to be valid and in bounds above.
Expand Down Expand Up @@ -344,6 +348,7 @@ unsafe impl<T> const SliceIndex<[T]> for ops::IndexRange {
}

#[inline]
#[rustc_no_writable]
fn index_mut(self, slice: &mut [T]) -> &mut [T] {
if self.end() <= slice.len() {
// SAFETY: `self` is checked to be valid and in bounds above.
Expand Down Expand Up @@ -376,6 +381,7 @@ unsafe impl<T> const SliceIndex<[T]> for ops::Range<usize> {
}

#[inline]
#[rustc_no_writable]
fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> {
if let Some(new_len) = usize::checked_sub(self.end, self.start)
&& self.end <= slice.len()
Expand Down Expand Up @@ -445,6 +451,7 @@ unsafe impl<T> const SliceIndex<[T]> for ops::Range<usize> {
}

#[inline]
#[rustc_no_writable]
fn index_mut(self, slice: &mut [T]) -> &mut [T] {
// Using checked_sub is a safe way to get `SubUnchecked` in MIR
if let Some(new_len) = usize::checked_sub(self.end, self.start)
Expand All @@ -469,6 +476,7 @@ unsafe impl<T> const SliceIndex<[T]> for range::Range<usize> {
}

#[inline]
#[rustc_no_writable]
fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> {
ops::Range::from(self).get_mut(slice)
}
Expand All @@ -491,6 +499,7 @@ unsafe impl<T> const SliceIndex<[T]> for range::Range<usize> {
}

#[inline]
#[rustc_no_writable]
fn index_mut(self, slice: &mut [T]) -> &mut [T] {
ops::Range::from(self).index_mut(slice)
}
Expand All @@ -508,6 +517,7 @@ unsafe impl<T> const SliceIndex<[T]> for ops::RangeTo<usize> {
}

#[inline]
#[rustc_no_writable]
fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> {
(0..self.end).get_mut(slice)
}
Expand All @@ -530,6 +540,7 @@ unsafe impl<T> const SliceIndex<[T]> for ops::RangeTo<usize> {
}

#[inline]
#[rustc_no_writable]
fn index_mut(self, slice: &mut [T]) -> &mut [T] {
(0..self.end).index_mut(slice)
}
Expand All @@ -547,6 +558,7 @@ unsafe impl<T> const SliceIndex<[T]> for ops::RangeFrom<usize> {
}

#[inline]
#[rustc_no_writable]
fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> {
(self.start..slice.len()).get_mut(slice)
}
Expand Down Expand Up @@ -576,6 +588,7 @@ unsafe impl<T> const SliceIndex<[T]> for ops::RangeFrom<usize> {
}

#[inline]
#[rustc_no_writable]
fn index_mut(self, slice: &mut [T]) -> &mut [T] {
if self.start > slice.len() {
slice_index_fail(self.start, slice.len(), slice.len())
Expand All @@ -599,6 +612,7 @@ unsafe impl<T> const SliceIndex<[T]> for range::RangeFrom<usize> {
}

#[inline]
#[rustc_no_writable]
fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> {
ops::RangeFrom::from(self).get_mut(slice)
}
Expand All @@ -621,6 +635,7 @@ unsafe impl<T> const SliceIndex<[T]> for range::RangeFrom<usize> {
}

#[inline]
#[rustc_no_writable]
fn index_mut(self, slice: &mut [T]) -> &mut [T] {
ops::RangeFrom::from(self).index_mut(slice)
}
Expand All @@ -637,6 +652,7 @@ unsafe impl<T> const SliceIndex<[T]> for ops::RangeFull {
}

#[inline]
#[rustc_no_writable]
fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> {
Some(slice)
}
Expand All @@ -657,6 +673,7 @@ unsafe impl<T> const SliceIndex<[T]> for ops::RangeFull {
}

#[inline]
#[rustc_no_writable]
fn index_mut(self, slice: &mut [T]) -> &mut [T] {
slice
}
Expand All @@ -676,6 +693,7 @@ unsafe impl<T> const SliceIndex<[T]> for ops::RangeInclusive<usize> {
}

#[inline]
#[rustc_no_writable]
fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> {
if *self.end() >= slice.len() { None } else { self.into_slice_range().get_mut(slice) }
}
Expand Down Expand Up @@ -708,6 +726,7 @@ unsafe impl<T> const SliceIndex<[T]> for ops::RangeInclusive<usize> {
}

#[inline]
#[rustc_no_writable]
fn index_mut(self, slice: &mut [T]) -> &mut [T] {
let Self { mut start, mut end, exhausted } = self;
let len = slice.len();
Expand All @@ -734,6 +753,7 @@ unsafe impl<T> const SliceIndex<[T]> for range::RangeInclusive<usize> {
}

#[inline]
#[rustc_no_writable]
fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> {
ops::RangeInclusive::from(self).get_mut(slice)
}
Expand All @@ -756,6 +776,7 @@ unsafe impl<T> const SliceIndex<[T]> for range::RangeInclusive<usize> {
}

#[inline]
#[rustc_no_writable]
fn index_mut(self, slice: &mut [T]) -> &mut [T] {
ops::RangeInclusive::from(self).index_mut(slice)
}
Expand All @@ -773,6 +794,7 @@ unsafe impl<T> const SliceIndex<[T]> for ops::RangeToInclusive<usize> {
}

#[inline]
#[rustc_no_writable]
fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> {
(0..=self.end).get_mut(slice)
}
Expand All @@ -795,6 +817,7 @@ unsafe impl<T> const SliceIndex<[T]> for ops::RangeToInclusive<usize> {
}

#[inline]
#[rustc_no_writable]
fn index_mut(self, slice: &mut [T]) -> &mut [T] {
(0..=self.end).index_mut(slice)
}
Expand All @@ -812,6 +835,7 @@ unsafe impl<T> const SliceIndex<[T]> for range::RangeToInclusive<usize> {
}

#[inline]
#[rustc_no_writable]
fn get_mut(self, slice: &mut [T]) -> Option<&mut [T]> {
(0..=self.last).get_mut(slice)
}
Expand All @@ -834,6 +858,7 @@ unsafe impl<T> const SliceIndex<[T]> for range::RangeToInclusive<usize> {
}

#[inline]
#[rustc_no_writable]
fn index_mut(self, slice: &mut [T]) -> &mut [T] {
(0..=self.last).index_mut(slice)
}
Expand Down Expand Up @@ -1047,6 +1072,7 @@ unsafe impl<T> SliceIndex<[T]> for (ops::Bound<usize>, ops::Bound<usize>) {
}

#[inline]
#[rustc_no_writable]
fn get_mut(self, slice: &mut [T]) -> Option<&mut Self::Output> {
try_into_slice_range(slice.len(), self)?.get_mut(slice)
}
Expand All @@ -1069,6 +1095,7 @@ unsafe impl<T> SliceIndex<[T]> for (ops::Bound<usize>, ops::Bound<usize>) {
}

#[inline]
#[rustc_no_writable]
fn index_mut(self, slice: &mut [T]) -> &mut Self::Output {
into_slice_range(slice.len(), self).index_mut(slice)
}
Expand Down
2 changes: 2 additions & 0 deletions library/core/src/slice/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,7 @@ impl<T> [T] {
#[inline]
#[must_use]
#[rustc_const_unstable(feature = "const_index", issue = "143775")]
#[rustc_no_writable]
pub const fn get_mut<I>(&mut self, index: I) -> Option<&mut I::Output>
where
I: [const] SliceIndex<Self>,
Expand Down Expand Up @@ -681,6 +682,7 @@ impl<T> [T] {
#[must_use]
#[track_caller]
#[rustc_const_unstable(feature = "const_index", issue = "143775")]
#[rustc_no_writable]
pub const unsafe fn get_unchecked_mut<I>(&mut self, index: I) -> &mut I::Output
where
I: [const] SliceIndex<Self>,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//@compile-flags: -Zmiri-tree-borrows -Zmiri-tree-borrows-implicit-writes

// This test reproduces the pattern used by `BorrowedCursor::as_mut`, which appears in `Socket::recv_with_flags` and `std::fs::read`.
// Many crates depend on similar patterns. Before https://github.com/rust-lang/rust/pull/157202 this failed under Tree
// Borrows with Implicit Writes. With the attribute `#[rustc_no_writable]` added to
// `slice::get_unchecked_mut`, both this test and the affected crates work.
fn borrowed_buf() {
struct BorrowedBuf<'a> {
buf: &'a mut [u8],
}

impl<'a> BorrowedBuf<'a> {
fn capacity(&self) -> usize {
self.buf.len()
}

unsafe fn as_mut(&mut self) -> &mut [u8] {
unsafe { self.buf.get_unchecked_mut(..) }
}
}

let mut arr = [0u8; 4];
let mut buf = BorrowedBuf { buf: &mut arr };

let ptr = unsafe { buf.as_mut() }.as_mut_ptr();
let _ = buf.capacity();
unsafe {
ptr.write(42);
}
}

// A variant of the above that uses `index_mut` notation.
fn index_mut() {
fn dostuff(x: &mut [u8]) {
unsafe {
let ptr = (&mut x[..]).as_mut_ptr();
let _len = x.len();
ptr.write(99);
}
}

dostuff(&mut [1, 2, 3, 4]);
}

fn main() {
borrowed_buf();
index_mut();
}
Loading