Skip to content

Commit 5423745

Browse files
committed
Auto merge of #105871 - llogiq:option-as-slice, r=scottmcm
Add `Option::as_`(`mut_`)`slice` This adds the following functions: * `Option<T>::as_slice(&self) -> &[T]` * `Option<T>::as_mut_slice(&mut self) -> &[T]` The `as_slice` and `as_mut_slice_mut` functions benefit from an optimization that makes them completely branch-free. ~~Unfortunately, this optimization is not available on by-value Options, therefore the `into_slice` implementations use the plain `match` + `slice::from_ref` approach.~~ Note that the optimization's soundness hinges on the fact that either the niche optimization makes the offset of the `Some(_)` contents zero or the mempory layout of `Option<T>` is equal to that of `Option<MaybeUninit<T>>`. The idea has been discussed on [Zulip](https://rust-lang.zulipchat.com/#narrow/stream/219381-t-libs/topic/Option.3A.3Aas_slice). Notably the idea for the `as_slice_mut` and `into_slice´ methods came from `@cuviper` and `@Sp00ph` hardened the optimization against niche-optimized Options. The [rust playground](https://play.rust-lang.org/?version=nightly&mode=release&edition=2021&gist=74f8e4239a19f454c183aaf7b4a969e0) shows that the generated assembly of the optimized method is basically only a copy while the naive method generates code containing a `test dx, dx` on x86_64. --- EDIT from reviewer: ACP is rust-lang/libs-team#150
2 parents 64165aa + 41da875 commit 5423745

File tree

3 files changed

+148
-0
lines changed

3 files changed

+148
-0
lines changed

library/core/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@
136136
#![feature(const_option)]
137137
#![feature(const_option_ext)]
138138
#![feature(const_pin)]
139+
#![feature(const_pointer_byte_offsets)]
139140
#![feature(const_pointer_is_aligned)]
140141
#![feature(const_ptr_sub_ptr)]
141142
#![feature(const_replace)]

library/core/src/option.rs

+119
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,7 @@ use crate::pin::Pin;
553553
use crate::{
554554
cmp, convert, hint, mem,
555555
ops::{self, ControlFlow, Deref, DerefMut},
556+
slice,
556557
};
557558

558559
/// The `Option` type. See [the module level documentation](self) for more.
@@ -734,6 +735,124 @@ impl<T> Option<T> {
734735
}
735736
}
736737

738+
const fn get_some_offset() -> isize {
739+
if mem::size_of::<Option<T>>() == mem::size_of::<T>() {
740+
// niche optimization means the `T` is always stored at the same position as the Option.
741+
0
742+
} else {
743+
assert!(mem::size_of::<Option<T>>() == mem::size_of::<Option<mem::MaybeUninit<T>>>());
744+
let some_uninit = Some(mem::MaybeUninit::<T>::uninit());
745+
// SAFETY: This gets the byte offset of the `Some(_)` value following the fact that
746+
// niche optimization is not active, and thus Option<T> and Option<MaybeUninit<t>> share
747+
// the same layout.
748+
unsafe {
749+
(some_uninit.as_ref().unwrap() as *const mem::MaybeUninit<T>)
750+
.byte_offset_from(&some_uninit as *const Option<mem::MaybeUninit<T>>)
751+
}
752+
}
753+
}
754+
755+
/// Returns a slice of the contained value, if any. If this is `None`, an
756+
/// empty slice is returned. This can be useful to have a single type of
757+
/// iterator over an `Option` or slice.
758+
///
759+
/// Note: Should you have an `Option<&T>` and wish to get a slice of `T`,
760+
/// you can unpack it via `opt.map_or(&[], std::slice::from_ref)`.
761+
///
762+
/// # Examples
763+
///
764+
/// ```rust
765+
/// #![feature(option_as_slice)]
766+
///
767+
/// assert_eq!(
768+
/// [Some(1234).as_slice(), None.as_slice()],
769+
/// [&[1234][..], &[][..]],
770+
/// );
771+
/// ```
772+
///
773+
/// The inverse of this function is (discounting
774+
/// borrowing) [`[_]::first`](slice::first):
775+
///
776+
/// ```rust
777+
/// #![feature(option_as_slice)]
778+
///
779+
/// for i in [Some(1234_u16), None] {
780+
/// assert_eq!(i.as_ref(), i.as_slice().first());
781+
/// }
782+
/// ```
783+
#[inline]
784+
#[must_use]
785+
#[unstable(feature = "option_as_slice", issue = "108545")]
786+
pub fn as_slice(&self) -> &[T] {
787+
// SAFETY: This is sound as long as `get_some_offset` returns the
788+
// correct offset. Though in the `None` case, the slice may be located
789+
// at a pointer pointing into padding, the fact that the slice is
790+
// empty, and the padding is at a properly aligned position for a
791+
// value of that type makes it sound.
792+
unsafe {
793+
slice::from_raw_parts(
794+
(self as *const Option<T>).wrapping_byte_offset(Self::get_some_offset())
795+
as *const T,
796+
self.is_some() as usize,
797+
)
798+
}
799+
}
800+
801+
/// Returns a mutable slice of the contained value, if any. If this is
802+
/// `None`, an empty slice is returned. This can be useful to have a
803+
/// single type of iterator over an `Option` or slice.
804+
///
805+
/// Note: Should you have an `Option<&mut T>` instead of a
806+
/// `&mut Option<T>`, which this method takes, you can obtain a mutable
807+
/// slice via `opt.map_or(&mut [], std::slice::from_mut)`.
808+
///
809+
/// # Examples
810+
///
811+
/// ```rust
812+
/// #![feature(option_as_slice)]
813+
///
814+
/// assert_eq!(
815+
/// [Some(1234).as_mut_slice(), None.as_mut_slice()],
816+
/// [&mut [1234][..], &mut [][..]],
817+
/// );
818+
/// ```
819+
///
820+
/// The result is a mutable slice of zero or one items that points into
821+
/// our original `Option`:
822+
///
823+
/// ```rust
824+
/// #![feature(option_as_slice)]
825+
///
826+
/// let mut x = Some(1234);
827+
/// x.as_mut_slice()[0] += 1;
828+
/// assert_eq!(x, Some(1235));
829+
/// ```
830+
///
831+
/// The inverse of this method (discounting borrowing)
832+
/// is [`[_]::first_mut`](slice::first_mut):
833+
///
834+
/// ```rust
835+
/// #![feature(option_as_slice)]
836+
///
837+
/// assert_eq!(Some(123).as_mut_slice().first_mut(), Some(&mut 123))
838+
/// ```
839+
#[inline]
840+
#[must_use]
841+
#[unstable(feature = "option_as_slice", issue = "108545")]
842+
pub fn as_mut_slice(&mut self) -> &mut [T] {
843+
// SAFETY: This is sound as long as `get_some_offset` returns the
844+
// correct offset. Though in the `None` case, the slice may be located
845+
// at a pointer pointing into padding, the fact that the slice is
846+
// empty, and the padding is at a properly aligned position for a
847+
// value of that type makes it sound.
848+
unsafe {
849+
slice::from_raw_parts_mut(
850+
(self as *mut Option<T>).wrapping_byte_offset(Self::get_some_offset()) as *mut T,
851+
self.is_some() as usize,
852+
)
853+
}
854+
}
855+
737856
/////////////////////////////////////////////////////////////////////////
738857
// Getting to contained values
739858
/////////////////////////////////////////////////////////////////////////

tests/codegen/option-as-slice.rs

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// compile-flags: -O
2+
// only-x86_64
3+
4+
#![crate_type = "lib"]
5+
#![feature(option_as_slice)]
6+
7+
extern crate core;
8+
9+
use core::num::NonZeroU64;
10+
use core::option::Option;
11+
12+
// CHECK-LABEL: @u64_opt_as_slice
13+
#[no_mangle]
14+
pub fn u64_opt_as_slice(o: &Option<u64>) -> &[u64] {
15+
// CHECK: start:
16+
// CHECK-NOT: select
17+
// CHECK: ret
18+
o.as_slice()
19+
}
20+
21+
// CHECK-LABEL: @nonzero_u64_opt_as_slice
22+
#[no_mangle]
23+
pub fn nonzero_u64_opt_as_slice(o: &Option<NonZeroU64>) -> &[NonZeroU64] {
24+
// CHECK: start:
25+
// CHECK-NOT: select
26+
// CHECK: ret
27+
o.as_slice()
28+
}

0 commit comments

Comments
 (0)