@@ -735,22 +735,43 @@ 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+ const SOME_BYTE_OFFSET_GUESS : isize = {
749+ let some_uninit = Some ( mem:: MaybeUninit :: < T > :: uninit ( ) ) ;
750+ let payload_ref = some_uninit. as_ref ( ) . unwrap ( ) ;
751+ // SAFETY: `as_ref` gives an address inside the existing `Option`,
752+ // so both pointers are derived from the same thing and the result
753+ // cannot overflow an `isize`.
754+ let offset = unsafe { <* const _ >:: byte_offset_from ( payload_ref, & some_uninit) } ;
755+
756+ // The offset is into the object, so it's guaranteed to be non-negative.
757+ assert ! ( offset >= 0 ) ;
758+
759+ // The payload and the overall option are aligned,
760+ // so the offset will be a multiple of the alignment too.
761+ assert ! ( ( offset as usize ) % mem:: align_of:: <T >( ) == 0 ) ;
762+
763+ let max_offset = mem:: size_of :: < Self > ( ) - mem:: size_of :: < T > ( ) ;
764+ if offset as usize <= max_offset {
765+ // The offset is at least inside the object, so let's try it.
766+ offset
742767 } 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- }
768+ // The offset guess is definitely wrong, so use the address
769+ // of the original option since we have it already.
770+ // This also correctly handles the case of layout-optimized enums
771+ // where `max_offset == 0` and thus this is the only possibility.
772+ 0
752773 }
753- }
774+ } ;
754775
755776 /// Returns a slice of the contained value, if any. If this is `None`, an
756777 /// empty slice is returned. This can be useful to have a single type of
@@ -784,18 +805,28 @@ impl<T> Option<T> {
784805 #[ must_use]
785806 #[ unstable( feature = "option_as_slice" , issue = "108545" ) ]
786807 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- }
808+ let payload_ptr: * const T =
809+ // The goal here is that both arms here are calculating exactly
810+ // the same pointer, and thus it'll be folded away when the guessed
811+ // offset is correct, but if the guess is wrong for some reason
812+ // it'll at least still be sound, just no longer optimal.
813+ if let Some ( payload) = self {
814+ payload
815+ } else {
816+ let self_ptr: * const Self = self ;
817+ // SAFETY: `SOME_BYTE_OFFSET_GUESS` guarantees that its value is
818+ // such that this will be in-bounds of the object.
819+ unsafe { self_ptr. byte_offset ( Self :: SOME_BYTE_OFFSET_GUESS ) . cast ( ) }
820+ } ;
821+ let len = usize:: from ( self . is_some ( ) ) ;
822+
823+ // SAFETY: When the `Option` is `Some`, we're using the actual pointer
824+ // to the payload, with a length of 1, so this is equivalent to
825+ // `slice::from_ref`, and thus is safe.
826+ // When the `Option` is `None`, the length used is 0, so to be safe it
827+ // just needs to be aligned, which it is because `&self` is aligned and
828+ // the offset used is a multiple of alignment.
829+ unsafe { slice:: from_raw_parts ( payload_ptr, len) }
799830 }
800831
801832 /// Returns a mutable slice of the contained value, if any. If this is
@@ -840,17 +871,28 @@ impl<T> Option<T> {
840871 #[ must_use]
841872 #[ unstable( feature = "option_as_slice" , issue = "108545" ) ]
842873 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- }
874+ let payload_ptr: * mut T =
875+ // The goal here is that both arms here are calculating exactly
876+ // the same pointer, and thus it'll be folded away when the guessed
877+ // offset is correct, but if the guess is wrong for some reason
878+ // it'll at least still be sound, just no longer optimal.
879+ if let Some ( payload) = self {
880+ payload
881+ } else {
882+ let self_ptr: * mut Self = self ;
883+ // SAFETY: `SOME_BYTE_OFFSET_GUESS` guarantees that its value is
884+ // such that this will be in-bounds of the object.
885+ unsafe { self_ptr. byte_offset ( Self :: SOME_BYTE_OFFSET_GUESS ) . cast ( ) }
886+ } ;
887+ let len = usize:: from ( self . is_some ( ) ) ;
888+
889+ // SAFETY: When the `Option` is `Some`, we're using the actual pointer
890+ // to the payload, with a length of 1, so this is equivalent to
891+ // `slice::from_mut`, and thus is safe.
892+ // When the `Option` is `None`, the length used is 0, so to be safe it
893+ // just needs to be aligned, which it is because `&self` is aligned and
894+ // the offset used is a multiple of alignment.
895+ unsafe { slice:: from_raw_parts_mut ( payload_ptr, len) }
854896 }
855897
856898 /////////////////////////////////////////////////////////////////////////
0 commit comments