@@ -735,22 +735,47 @@ impl<T> Option<T> {
735
735
}
736
736
}
737
737
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
742
771
} 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
752
777
}
753
- }
778
+ } ;
754
779
755
780
/// Returns a slice of the contained value, if any. If this is `None`, an
756
781
/// empty slice is returned. This can be useful to have a single type of
@@ -784,18 +809,28 @@ impl<T> Option<T> {
784
809
#[ must_use]
785
810
#[ unstable( feature = "option_as_slice" , issue = "108545" ) ]
786
811
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) }
799
834
}
800
835
801
836
/// Returns a mutable slice of the contained value, if any. If this is
@@ -840,17 +875,28 @@ impl<T> Option<T> {
840
875
#[ must_use]
841
876
#[ unstable( feature = "option_as_slice" , issue = "108545" ) ]
842
877
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) }
854
900
}
855
901
856
902
/////////////////////////////////////////////////////////////////////////
0 commit comments