Change:
Feature gate: This is a tracking issue for the and methods. The functions return an immutable or mutable slice to the value contained in the Option, if any. Otherwise an empty slice is returned. [x] Implementation: [x] Final comment period (FCP)[^1] [x] Stabilization PR The implementation now uses an approach that's always sound (just less efficient than optimal if the hack guesses wrong). A stabilization conversation would probably still want to discuss whether we're comfortable with shipping that approach as stable (since the implementation could be improved non-breakingly later) or we'd prefer to wait for a more principled approach (such as an extension of from that allows enum variants) first. [^1]: https://std-dev-
Updated the discussion in the OP now that the implementation relies on the layout estimate only for performance, not for soundness.
I'll push another version using an intrinsic shortly, so we'll have performance and soundness. Edit:
Superceded by with a more direct approach using an intrinsic instead of a hacky estimate.
The initial implementation was in fixed a potential soundness issue moved the implementation to an intrinsic (Future) There may be a future PR which removes the intrinsic in favor of once that is available for enums The API is summarized at the . Shortly after the initial implementation, dogfooded the feature in the compiler, adding 8 lines and removing 17. All four of the cases were both perf and legibility wins. The author notes that there he found some other cases outside of rustc where the feature could be applied, though isn't yet because of not being stabilized, even though his search was merely for a naive implementation of the feature, there may be cases where people have resorted to using a or other type instead of an to get . Those cases aren't as easy to find though. Stabilizing this feature will bring the standard library up to parity with the author's crate (though that is only for bools, ints and floats). The API as it is now mirrors that of other collections (notably array, , and as well as various of their iterators. The only possible extension that was hinted at during development was to into a possibly empty slice, but this wasn't attempted due to being a breaking change. Finally, I want to note that we can still change the implementation once on enums is available, as the API surface will stay the same. So this seems like as good a time as any to stabilize this feature. Shall we? ping
merge
Team member has proposed to merge this. The next step is review by the rest of the tagged team members: [x] [x] [x] [ ] [x] No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See for info about what commands tagged team members can give me.
:bell: This is now entering its final comment period, as per the . :bell:
Is it a hard requirement for an empty slice to point to the same place as if was ? Because the pointer part of slice just need to be non-null and sufficiently aligned, so using the address of an itself (as well as ) is always the sound approach.
Well, the API doesn't guarantee it in the docs, so I don't think it's a hard requirement. It's an important QoI detail for performance, though. By returning the empty slice pointing at where the item would be, it's always returning the same pointer. That simplifies codegen and optimizations, by not needing the extra conditional instruction. Compare these:
As there were no concerns brought up so far, I'll likely push a stabilization PR later today.
The final comment period, with a disposition to merge, as per the , is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. This will be merged soon.
This should be closed as the feature was stabilized.

Discussion:
} } <del> const fn get_some_offset() -> isize { if mem::size_of::<Option<T>>() == mem::size_of::<T>() { // niche optimization means the `T` is always stored at the same position as the Option. 0 </del> <ins> /// This is a guess at how many bytes into the option the payload can be found. /// /// For niche-optimized types it's correct because it's pigeon-holed to only /// one possible place.  For other types, it's usually correct today, but /// tweaks to the layout algorithm (particularly expansions of /// `-Z randomize-layout`) might make it incorrect at any point. /// /// It's guaranteed to be a multiple of alignment (so will always give a /// correctly-aligned location) and to be within the allocated object, so /// is valid to use with `offset` and to use for a zero-sized read. /// /// FIXME: This is a horrible hack, but allows a nice optimization.  It should /// be replaced with `offset_of!` once that works on enum variants. const SOME_BYTE_OFFSET_GUESS: isize = { let some_uninit = Some(mem::MaybeUninit::<T>::uninit()); let payload_ref = some_uninit.as_ref().unwrap(); // SAFETY: `as_ref` gives an address inside the existing `Option`, // so both pointers are derived from the same thing and the result // cannot overflow an `isize`. let offset = unsafe { <*const _>::byte_offset_from(payload_ref, &some_uninit) }; // The offset is into the object, so it's guaranteed to be non-negative. assert!(offset >= 0); // The payload and the overall option are aligned, // so the offset will be a multiple of the alignment too. assert!((offset as usize) % mem::align_of::<T>() == 0); let max_offset = mem::size_of::<Self>() - mem::size_of::<T>(); if offset as usize <= max_offset { // There's enough space after this offset for a `T` to exist without // overflowing the bounds of the object, so let's try it. offset </ins> } else { <del> assert!(mem::size_of::<Option<T>>() == mem::size_of::<Option<mem::MaybeUninit<T>>>()); let some_uninit = Some(mem::MaybeUninit::<T>::uninit()); // SAFETY: This gets the byte offset of the `Some(_)` value following the fact that // niche optimization is not active, and thus Option<T> and Option<MaybeUninit<t>> share // the same layout. unsafe { (some_uninit.as_ref().unwrap() as *const mem::MaybeUninit<T>) .byte_offset_from(&some_uninit as *const Option<mem::MaybeUninit<T>>) } </del> <ins> // The offset guess is definitely wrong, so use the address // of the original option since we have it already. // This also correctly handles the case of layout-optimized enums // where `max_offset == 0` and thus this is the only possibility. 0 </ins> } <del> } </del> <ins> }; </ins> /// Returns a slice of the contained value, if any. If this is `None`, an /// empty slice is returned. This can be useful to have a single type of

#[must_use] #[unstable(feature = "option_as_slice", issue = "108545")] pub fn as_slice(&self) -> &[T] { <del> // SAFETY: This is sound as long as `get_some_offset` returns the // correct offset. Though in the `None` case, the slice may be located // at a pointer pointing into padding, the fact that the slice is // empty, and the padding is at a properly aligned position for a // value of that type makes it sound. unsafe { slice::from_raw_parts( (self as *const Option<T>).wrapping_byte_offset(Self::get_some_offset()) as *const T, self.is_some() as usize, ) } </del> <ins> let payload_ptr: *const T = // The goal here is that both arms here are calculating exactly // the same pointer, and thus it'll be folded away when the guessed // offset is correct, but if the guess is wrong for some reason // it'll at least still be sound, just no longer optimal. if let Some(payload) = self { payload } else { let self_ptr: *const Self = self; // SAFETY: `SOME_BYTE_OFFSET_GUESS` guarantees that its value is // such that this will be in-bounds of the object. unsafe { self_ptr.byte_offset(Self::SOME_BYTE_OFFSET_GUESS).cast() } }; let len = usize::from(self.is_some()); // SAFETY: When the `Option` is `Some`, we're using the actual pointer // to the payload, with a length of 1, so this is equivalent to // `slice::from_ref`, and thus is safe. // When the `Option` is `None`, the length used is 0, so to be safe it // just needs to be aligned, which it is because `&self` is aligned and // the offset used is a multiple of alignment. unsafe { slice::from_raw_parts(payload_ptr, len) } </ins> } /// Returns a mutable slice of the contained value, if any. If this is

#[must_use] #[unstable(feature = "option_as_slice", issue = "108545")] pub fn as_mut_slice(&mut self) -> &mut [T] { <del> // SAFETY: This is sound as long as `get_some_offset` returns the // correct offset. Though in the `None` case, the slice may be located // at a pointer pointing into padding, the fact that the slice is // empty, and the padding is at a properly aligned position for a // value of that type makes it sound. unsafe { slice::from_raw_parts_mut( (self as *mut Option<T>).wrapping_byte_offset(Self::get_some_offset()) as *mut T, self.is_some() as usize, ) } </del> <ins> let payload_ptr: *mut T = // The goal here is that both arms here are calculating exactly // the same pointer, and thus it'll be folded away when the guessed // offset is correct, but if the guess is wrong for some reason // it'll at least still be sound, just no longer optimal. if let Some(payload) = self { payload } else { let self_ptr: *mut Self = self; // SAFETY: `SOME_BYTE_OFFSET_GUESS` guarantees that its value is // such that this will be in-bounds of the object. unsafe { self_ptr.byte_offset(Self::SOME_BYTE_OFFSET_GUESS).cast() } }; let len = usize::from(self.is_some()); // SAFETY: When the `Option` is `Some`, we're using the actual pointer // to the payload, with a length of 1, so this is equivalent to // `slice::from_mut`, and thus is safe. // When the `Option` is `None`, the length used is 0, so to be safe it // just needs to be aligned, which it is because `&self` is aligned and // the offset used is a multiple of alignment. unsafe { slice::from_raw_parts_mut(payload_ptr, len) } </ins> } /////////////////////////////////////////////////////////////////////////

<del> // compile-flags: -O </del> <ins> // compile-flags: -O -Z randomize-layout=no </ins> // only-x86_64 #![crate_type = "lib"]

// CHECK-LABEL: @u64_opt_as_slice #[no_mangle] pub fn u64_opt_as_slice(o: &Option<u64>) -> &[u64] { <del> // CHECK: start: </del> // CHECK-NOT: select <del> // CHECK: ret </del> <ins> // CHECK-NOT: br // CHECK-NOT: switch // CHECK-NOT: icmp </ins> o.as_slice() } // CHECK-LABEL: @nonzero_u64_opt_as_slice #[no_mangle] pub fn nonzero_u64_opt_as_slice(o: &Option<NonZeroU64>) -> &[NonZeroU64] { <del> // CHECK: start: </del> // CHECK-NOT: select <del> // CHECK: ret </del> <ins> // CHECK-NOT: br // CHECK-NOT: switch // CHECK-NOT: icmp // CHECK: %[[NZ:.+]] = icmp ne i64 %{{.+}}, 0 // CHECK-NEXT: zext i1 %[[NZ]] to i64 // CHECK-NOT: select // CHECK-NOT: br // CHECK-NOT: switch // CHECK-NOT: icmp </ins> o.as_slice() }