Skip to content

Commit ebdc794

Browse files
committed
Add offset-ish convenience methods to NonNull
1 parent 36a587f commit ebdc794

File tree

2 files changed

+274
-0
lines changed

2 files changed

+274
-0
lines changed

Diff for: library/core/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@
178178
#![feature(is_ascii_octdigit)]
179179
#![feature(isqrt)]
180180
#![feature(maybe_uninit_uninit_array)]
181+
#![feature(non_null_convenience)]
181182
#![feature(offset_of)]
182183
#![feature(offset_of_enum)]
183184
#![feature(ptr_alignment_type)]

Diff for: library/core/src/ptr/non_null.rs

+273
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,279 @@ impl<T: ?Sized> NonNull<T> {
474474
unsafe { NonNull::new_unchecked(self.as_ptr() as *mut U) }
475475
}
476476

477+
/// Calculates the offset from a pointer.
478+
///
479+
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer
480+
/// offset of `3 * size_of::<T>()` bytes.
481+
///
482+
/// # Safety
483+
///
484+
/// If any of the following conditions are violated, the result is Undefined
485+
/// Behavior:
486+
///
487+
/// * Both the starting and resulting pointer must be either in bounds or one
488+
/// byte past the end of the same [allocated object].
489+
///
490+
/// * The computed offset, **in bytes**, cannot overflow an `isize`.
491+
///
492+
/// * The offset being in bounds cannot rely on "wrapping around" the address
493+
/// space. That is, the infinite-precision sum, **in bytes** must fit in a usize.
494+
///
495+
/// The compiler and standard library generally tries to ensure allocations
496+
/// never reach a size where an offset is a concern. For instance, `Vec`
497+
/// and `Box` ensure they never allocate more than `isize::MAX` bytes, so
498+
/// `vec.as_ptr().add(vec.len())` is always safe.
499+
///
500+
/// Most platforms fundamentally can't even construct such an allocation.
501+
/// For instance, no known 64-bit platform can ever serve a request
502+
/// for 2<sup>63</sup> bytes due to page-table limitations or splitting the address space.
503+
/// However, some 32-bit and 16-bit platforms may successfully serve a request for
504+
/// more than `isize::MAX` bytes with things like Physical Address
505+
/// Extension. As such, memory acquired directly from allocators or memory
506+
/// mapped files *may* be too large to handle with this function.
507+
///
508+
/// [allocated object]: crate::ptr#allocated-object
509+
///
510+
/// # Examples
511+
///
512+
/// ```
513+
/// #![feature(non_null_convenience)]
514+
/// use std::ptr::NonNull;
515+
///
516+
/// let mut s = [1, 2, 3];
517+
/// let ptr: NonNull<u32> = NonNull::new(s.as_mut_ptr()).unwrap();
518+
///
519+
/// unsafe {
520+
/// println!("{}", ptr.offset(1).read());
521+
/// println!("{}", ptr.offset(2).read());
522+
/// }
523+
/// ```
524+
#[unstable(feature = "non_null_convenience", issue = "117691")]
525+
#[rustc_const_unstable(feature = "non_null_convenience", issue = "117691")]
526+
#[must_use = "returns a new pointer rather than modifying its argument"]
527+
#[inline(always)]
528+
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
529+
pub const unsafe fn offset(self, count: isize) -> NonNull<T>
530+
where
531+
T: Sized,
532+
{
533+
// SAFETY: the caller must uphold the safety contract for `offset`.
534+
// Additionally safety contract of `offset` guarantees that the resulting pointer is
535+
// pointing to an allocation, there can't be an allocation at null, thus it's safe to
536+
// construct `NonNull`.
537+
unsafe { NonNull { pointer: intrinsics::offset(self.pointer, count) } }
538+
}
539+
540+
/// Calculates the offset from a pointer in bytes.
541+
///
542+
/// `count` is in units of **bytes**.
543+
///
544+
/// This is purely a convenience for casting to a `u8` pointer and
545+
/// using [offset][pointer::offset] on it. See that method for documentation
546+
/// and safety requirements.
547+
///
548+
/// For non-`Sized` pointees this operation changes only the data pointer,
549+
/// leaving the metadata untouched.
550+
#[unstable(feature = "non_null_convenience", issue = "117691")]
551+
#[rustc_const_unstable(feature = "non_null_convenience", issue = "117691")]
552+
#[must_use]
553+
#[inline(always)]
554+
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
555+
pub const unsafe fn byte_offset(self, count: isize) -> Self {
556+
// SAFETY: the caller must uphold the safety contract for `offset` and `byte_offset` has
557+
// the same safety contract.
558+
// Additionally safety contract of `offset` guarantees that the resulting pointer is
559+
// pointing to an allocation, there can't be an allocation at null, thus it's safe to
560+
// construct `NonNull`.
561+
unsafe { NonNull { pointer: self.pointer.byte_offset(count) } }
562+
}
563+
564+
/// Calculates the offset from a pointer (convenience for `.offset(count as isize)`).
565+
///
566+
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer
567+
/// offset of `3 * size_of::<T>()` bytes.
568+
///
569+
/// # Safety
570+
///
571+
/// If any of the following conditions are violated, the result is Undefined
572+
/// Behavior:
573+
///
574+
/// * Both the starting and resulting pointer must be either in bounds or one
575+
/// byte past the end of the same [allocated object].
576+
///
577+
/// * The computed offset, **in bytes**, cannot overflow an `isize`.
578+
///
579+
/// * The offset being in bounds cannot rely on "wrapping around" the address
580+
/// space. That is, the infinite-precision sum must fit in a `usize`.
581+
///
582+
/// The compiler and standard library generally tries to ensure allocations
583+
/// never reach a size where an offset is a concern. For instance, `Vec`
584+
/// and `Box` ensure they never allocate more than `isize::MAX` bytes, so
585+
/// `vec.as_ptr().add(vec.len())` is always safe.
586+
///
587+
/// Most platforms fundamentally can't even construct such an allocation.
588+
/// For instance, no known 64-bit platform can ever serve a request
589+
/// for 2<sup>63</sup> bytes due to page-table limitations or splitting the address space.
590+
/// However, some 32-bit and 16-bit platforms may successfully serve a request for
591+
/// more than `isize::MAX` bytes with things like Physical Address
592+
/// Extension. As such, memory acquired directly from allocators or memory
593+
/// mapped files *may* be too large to handle with this function.
594+
///
595+
/// [allocated object]: crate::ptr#allocated-object
596+
///
597+
/// # Examples
598+
///
599+
/// ```
600+
/// #![feature(non_null_convenience)]
601+
/// use std::ptr::NonNull;
602+
///
603+
/// let s: &str = "123";
604+
/// let ptr: NonNull<u8> = NonNull::new(s.as_ptr().cast_mut()).unwrap();
605+
///
606+
/// unsafe {
607+
/// println!("{}", ptr.add(1).read() as char);
608+
/// println!("{}", ptr.add(2).read() as char);
609+
/// }
610+
/// ```
611+
#[unstable(feature = "non_null_convenience", issue = "117691")]
612+
#[rustc_const_unstable(feature = "non_null_convenience", issue = "117691")]
613+
#[must_use = "returns a new pointer rather than modifying its argument"]
614+
#[inline(always)]
615+
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
616+
pub const unsafe fn add(self, count: usize) -> Self
617+
where
618+
T: Sized,
619+
{
620+
// SAFETY: the caller must uphold the safety contract for `offset`.
621+
// Additionally safety contract of `offset` guarantees that the resulting pointer is
622+
// pointing to an allocation, there can't be an allocation at null, thus it's safe to
623+
// construct `NonNull`.
624+
unsafe { NonNull { pointer: intrinsics::offset(self.pointer, count) } }
625+
}
626+
627+
/// Calculates the offset from a pointer in bytes (convenience for `.byte_offset(count as isize)`).
628+
///
629+
/// `count` is in units of bytes.
630+
///
631+
/// This is purely a convenience for casting to a `u8` pointer and
632+
/// using [`add`][NonNull::add] on it. See that method for documentation
633+
/// and safety requirements.
634+
///
635+
/// For non-`Sized` pointees this operation changes only the data pointer,
636+
/// leaving the metadata untouched.
637+
#[unstable(feature = "non_null_convenience", issue = "117691")]
638+
#[rustc_const_unstable(feature = "non_null_convenience", issue = "117691")]
639+
#[must_use]
640+
#[inline(always)]
641+
#[rustc_allow_const_fn_unstable(set_ptr_value)]
642+
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
643+
pub const unsafe fn byte_add(self, count: usize) -> Self {
644+
// SAFETY: the caller must uphold the safety contract for `add` and `byte_add` has the same
645+
// safety contract.
646+
// Additionally safety contract of `add` guarantees that the resulting pointer is pointing
647+
// to an allocation, there can't be an allocation at null, thus it's safe to construct
648+
// `NonNull`.
649+
unsafe { NonNull { pointer: self.pointer.byte_add(count) } }
650+
}
651+
652+
/// Calculates the offset from a pointer (convenience for
653+
/// `.offset((count as isize).wrapping_neg())`).
654+
///
655+
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer
656+
/// offset of `3 * size_of::<T>()` bytes.
657+
///
658+
/// # Safety
659+
///
660+
/// If any of the following conditions are violated, the result is Undefined
661+
/// Behavior:
662+
///
663+
/// * Both the starting and resulting pointer must be either in bounds or one
664+
/// byte past the end of the same [allocated object].
665+
///
666+
/// * The computed offset cannot exceed `isize::MAX` **bytes**.
667+
///
668+
/// * The offset being in bounds cannot rely on "wrapping around" the address
669+
/// space. That is, the infinite-precision sum must fit in a usize.
670+
///
671+
/// The compiler and standard library generally tries to ensure allocations
672+
/// never reach a size where an offset is a concern. For instance, `Vec`
673+
/// and `Box` ensure they never allocate more than `isize::MAX` bytes, so
674+
/// `vec.as_ptr().add(vec.len()).sub(vec.len())` is always safe.
675+
///
676+
/// Most platforms fundamentally can't even construct such an allocation.
677+
/// For instance, no known 64-bit platform can ever serve a request
678+
/// for 2<sup>63</sup> bytes due to page-table limitations or splitting the address space.
679+
/// However, some 32-bit and 16-bit platforms may successfully serve a request for
680+
/// more than `isize::MAX` bytes with things like Physical Address
681+
/// Extension. As such, memory acquired directly from allocators or memory
682+
/// mapped files *may* be too large to handle with this function.
683+
///
684+
/// [allocated object]: crate::ptr#allocated-object
685+
///
686+
/// # Examples
687+
///
688+
/// ```
689+
/// #![feature(non_null_convenience)]
690+
/// use std::ptr::NonNull;
691+
///
692+
/// let s: &str = "123";
693+
///
694+
/// unsafe {
695+
/// let end: NonNull<u8> = NonNull::new(s.as_ptr().cast_mut()).unwrap().add(3);
696+
/// println!("{}", end.sub(1).read() as char);
697+
/// println!("{}", end.sub(2).read() as char);
698+
/// }
699+
/// ```
700+
#[unstable(feature = "non_null_convenience", issue = "117691")]
701+
#[rustc_const_unstable(feature = "non_null_convenience", issue = "117691")]
702+
#[must_use = "returns a new pointer rather than modifying its argument"]
703+
// We could always go back to wrapping if unchecked becomes unacceptable
704+
#[rustc_allow_const_fn_unstable(const_int_unchecked_arith)]
705+
#[inline(always)]
706+
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
707+
pub const unsafe fn sub(self, count: usize) -> Self
708+
where
709+
T: Sized,
710+
{
711+
if T::IS_ZST {
712+
// Pointer arithmetic does nothing when the pointee is a ZST.
713+
self
714+
} else {
715+
// SAFETY: the caller must uphold the safety contract for `offset`.
716+
// Because the pointee is *not* a ZST, that means that `count` is
717+
// at most `isize::MAX`, and thus the negation cannot overflow.
718+
unsafe { self.offset(intrinsics::unchecked_sub(0, count as isize)) }
719+
}
720+
}
721+
722+
/// Calculates the offset from a pointer in bytes (convenience for
723+
/// `.byte_offset((count as isize).wrapping_neg())`).
724+
///
725+
/// `count` is in units of bytes.
726+
///
727+
/// This is purely a convenience for casting to a `u8` pointer and
728+
/// using [`sub`][NonNull::sub] on it. See that method for documentation
729+
/// and safety requirements.
730+
///
731+
/// For non-`Sized` pointees this operation changes only the data pointer,
732+
/// leaving the metadata untouched.
733+
#[unstable(feature = "non_null_convenience", issue = "117691")]
734+
#[rustc_const_unstable(feature = "non_null_convenience", issue = "117691")]
735+
#[must_use]
736+
#[inline(always)]
737+
#[rustc_allow_const_fn_unstable(set_ptr_value)]
738+
#[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
739+
pub const unsafe fn byte_sub(self, count: usize) -> Self {
740+
// SAFETY: the caller must uphold the safety contract for `sub` and `byte_sub` has the same
741+
// safety contract.
742+
// Additionally safety contract of `sub` guarantees that the resulting pointer is pointing
743+
// to an allocation, there can't be an allocation at null, thus it's safe to construct
744+
// `NonNull`.
745+
unsafe { NonNull { pointer: self.pointer.byte_sub(count) } }
746+
}
747+
748+
// N.B. `wrapping_offset``, `wrapping_add`, etc are not implemented because they can wrap to null
749+
477750
/// Reads the value from `self` without moving it. This leaves the
478751
/// memory in `self` unchanged.
479752
///

0 commit comments

Comments
 (0)