@@ -735,22 +735,47 @@ impl<T> Option<T> {
735735 }
736736 }
737737
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
738+ /// This is a guess at how many bytes into the option the payload can be found.
739+ ///
740+ /// For niche-optimized types it's correct because it's pigeon-holed to only
741+ /// one possible place. For other types, it's usually correct today, but
742+ /// tweaks to the layout algorithm (particularly expansions of
743+ /// `-Z randomize-layout`) might make it incorrect at any point.
744+ ///
745+ /// It's guaranteed to be a multiple of alignment (so will always give a
746+ /// correctly-aligned location) and to be within the allocated object, so
747+ /// is valid to use with `offset` and to use for a zero-sized read.
748+ ///
749+ /// FIXME: This is a horrible hack, but allows a nice optimization. It should
750+ /// be replaced with `offset_of!` once that works on enum variants.
751+ const SOME_BYTE_OFFSET_GUESS : isize = {
752+ let some_uninit = Some ( mem:: MaybeUninit :: < T > :: uninit ( ) ) ;
753+ let payload_ref = some_uninit. as_ref ( ) . unwrap ( ) ;
754+ // SAFETY: `as_ref` gives an address inside the existing `Option`,
755+ // so both pointers are derived from the same thing and the result
756+ // cannot overflow an `isize`.
757+ let offset = unsafe { <* const _ >:: byte_offset_from ( payload_ref, & some_uninit) } ;
758+
759+ // The offset is into the object, so it's guaranteed to be non-negative.
760+ assert ! ( offset >= 0 ) ;
761+
762+ // The payload and the overall option are aligned,
763+ // so the offset will be a multiple of the alignment too.
764+ assert ! ( ( offset as usize ) % mem:: align_of:: <T >( ) == 0 ) ;
765+
766+ let max_offset = mem:: size_of :: < Self > ( ) - mem:: size_of :: < T > ( ) ;
767+ if offset as usize <= max_offset {
768+ // There's enough space after this offset for a `T` to exist without
769+ // overflowing the bounds of the object, so let's try it.
770+ offset
742771 } 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- }
772+ // The offset guess is definitely wrong, so use the address
773+ // of the original option since we have it already.
774+ // This also correctly handles the case of layout-optimized enums
775+ // where `max_offset == 0` and thus this is the only possibility.
776+ 0
752777 }
753- }
778+ } ;
754779
755780 /// Returns a slice of the contained value, if any. If this is `None`, an
756781 /// empty slice is returned. This can be useful to have a single type of
@@ -784,18 +809,28 @@ impl<T> Option<T> {
784809 #[ must_use]
785810 #[ unstable( feature = "option_as_slice" , issue = "108545" ) ]
786811 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- }
812+ let payload_ptr: * const T =
813+ // The goal here is that both arms here are calculating exactly
814+ // the same pointer, and thus it'll be folded away when the guessed
815+ // offset is correct, but if the guess is wrong for some reason
816+ // it'll at least still be sound, just no longer optimal.
817+ if let Some ( payload) = self {
818+ payload
819+ } else {
820+ let self_ptr: * const Self = self ;
821+ // SAFETY: `SOME_BYTE_OFFSET_GUESS` guarantees that its value is
822+ // such that this will be in-bounds of the object.
823+ unsafe { self_ptr. byte_offset ( Self :: SOME_BYTE_OFFSET_GUESS ) . cast ( ) }
824+ } ;
825+ let len = usize:: from ( self . is_some ( ) ) ;
826+
827+ // SAFETY: When the `Option` is `Some`, we're using the actual pointer
828+ // to the payload, with a length of 1, so this is equivalent to
829+ // `slice::from_ref`, and thus is safe.
830+ // When the `Option` is `None`, the length used is 0, so to be safe it
831+ // just needs to be aligned, which it is because `&self` is aligned and
832+ // the offset used is a multiple of alignment.
833+ unsafe { slice:: from_raw_parts ( payload_ptr, len) }
799834 }
800835
801836 /// Returns a mutable slice of the contained value, if any. If this is
@@ -840,17 +875,28 @@ impl<T> Option<T> {
840875 #[ must_use]
841876 #[ unstable( feature = "option_as_slice" , issue = "108545" ) ]
842877 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- }
878+ let payload_ptr: * mut T =
879+ // The goal here is that both arms here are calculating exactly
880+ // the same pointer, and thus it'll be folded away when the guessed
881+ // offset is correct, but if the guess is wrong for some reason
882+ // it'll at least still be sound, just no longer optimal.
883+ if let Some ( payload) = self {
884+ payload
885+ } else {
886+ let self_ptr: * mut Self = self ;
887+ // SAFETY: `SOME_BYTE_OFFSET_GUESS` guarantees that its value is
888+ // such that this will be in-bounds of the object.
889+ unsafe { self_ptr. byte_offset ( Self :: SOME_BYTE_OFFSET_GUESS ) . cast ( ) }
890+ } ;
891+ let len = usize:: from ( self . is_some ( ) ) ;
892+
893+ // SAFETY: When the `Option` is `Some`, we're using the actual pointer
894+ // to the payload, with a length of 1, so this is equivalent to
895+ // `slice::from_mut`, and thus is safe.
896+ // When the `Option` is `None`, the length used is 0, so to be safe it
897+ // just needs to be aligned, which it is because `&self` is aligned and
898+ // the offset used is a multiple of alignment.
899+ unsafe { slice:: from_raw_parts_mut ( payload_ptr, len) }
854900 }
855901
856902 /////////////////////////////////////////////////////////////////////////
0 commit comments