Skip to content

Option<NonNull<T>>::as_ptr #872

Open

Description

@wyfo

Proposal

Problem statement

(Following https://internals.rust-lang.org/t/option-nonnull-t-as-ptr/24563)
When working with raw pointers, using NonNull<T> and sometimes Option<NonNull<T>> can be more ergonomic. However, some APIs, e.g. AtomicPtr, requires raw pointers. NonNull has a as_ptr method, but when it comes to Option<NonNull<T>>, map_or(ptr::null_mut(), NonNull::as_ptr)` is cumbersome.

Motivating examples or use cases

fn foo(atomic: &AtomicPtr<()>, ptr: Option<NonNull<()>>) {
    atomic.store(ptr.map_or(ptr::null_mut(), NonNull::as_ptr), Relaxed); // this pattern can appears a lot in codebases mixing NonNull and AtomicPtr
}

Solution sketch

impl<T: ?Sized> Option<NonNull<T>> {
    pub fn as_ptr(self) -> *mut T {
        match self {
            Some(non_null) => non_null.as_ptr(),
            None => core::ptr::null_mut(),
        }
    }
}

Alternatives

The main alternative is to not use Option<NonNull<T>> in the first place and keep raw pointers when they are nullable. However, Option<NonNull<T>> comes with some ergonomic advantages (? operator, Option methods, None constant, etc.) which would then be lost.

The other alternative is to keep the statu quo, i.e. map_or(ptr::null_mut(), NonNull::as_ptr), and let users using it extensively make their own extension trait (which I'm doing in my own crates). However, Option already have a few specialized methods, like Option<(T, U)>::unzip, so Option<NonNull<T>>::as_ptr feels natural to add.

Links and related work

Following https://internals.rust-lang.org/t/option-nonnull-t-as-ptr/24563

What happens now?

This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.

Possible responses

The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):

  • We think this problem seems worth solving, and the standard library might be the right place to solve it.
  • We think that this probably doesn't belong in the standard library.

Second, if there's a concrete solution:

  • We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
  • We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.

Activity

  1. added
    api-change-proposalA proposal to add or alter unstable APIs in the standard libraries
    on Aug 30, 2026
  2. hanna-kruppe commented on Aug 30, 2026

    @hanna-kruppe

    The name as_ptr is unfortunately closely related to as_ref which means something else entirely on Option. I’m actually surprised we don’t already have Option::as_ptr as raw pointer equivalent of Option::as_ref (I guess it’s not very useful in practice?).

    In the internals thread, @scottmcm suggested or_zero for the Option<NonZero<T>> -> T variant and I like that. So maybe the NonNull related method should be called or_null()? I guess one could also make an argument for unwrap_or_{zero,null} (although it does change the element type, unlike existing unwrap_or_* methods that go from Option<T> to T).

  3. cuviper commented on Aug 30, 2026

    @cuviper
    Member

    I like or_null and or_zero too.

  4. scottmcm commented on Aug 30, 2026

    @scottmcm
    Member

    I was pondering whether we ought to have both .or_null() and .or_null_mut(). On the one hand, just or_null returning *mut is probably fine since it'll coerce to *const if needed, but the other hand, that argument could have applied for ptr::null() too but that still has ptr::null_mut().

    I guess that since NonNull::as_ptr returns *mut, it's reasonable for Option<NonNull<_>>::or_null to also return *mut.


    On the implementation side, I've definitely sometimes thought it silly to write out a match to do things like this when I know it's just a transmute, but the value of safety outweighs it. So one nice thing about having this method is that it'd be a place to do that transmute once but let other safe code take advantage of it -- same as how NonZero::new is just a transmute under the hood now, and this method is thus the inverse of that one.

    (LLVM can, of course, easily turn the blocks into a select and then the select into a nop, but it's nice to not need to.)


    I guess one could also make an argument for unwrap_or_{zero,null} (although it does change the element type, unlike existing unwrap_or_* methods that go from Option<T> to T).

    I think if I saw .unwrap_or_zero() my first reaction would be "why did they bother adding that when .unwrap_or(0) exists?" so giving it a more punchy name without needing the scary "unwrap" could help emphasize the point more.

    (Compare .unwrap_or_default() which is literally just .unwrap_or_else(default).)

  5. clarfonthey commented on Aug 30, 2026

    @clarfonthey
    Contributor

    So, was curious and opened rust-lang/rust#162029 to see if we could just add From impls for these.

    For the NonZero case (which I added myself, since I also use it a fair bit), I think it would be very natural to add a fn get(self) -> T for Option<NonZero<T>> to mirror the same method on NonZero itself. But for NonNull, that method would be as_ptr, so, I'm a little less sure how that should go.

    Either way, I figure that .into() should Just Work most of the time, maybe?

  6. Mark-Simulacrum commented on Aug 30, 2026

    @Mark-Simulacrum
    Member

    IMO, .into() is much less readable in that position and has bad inference effects if we add other impls; I don't think we should encourage its use.

    Since we brought up NonZero, I'll note that one place something like this comes up for me is spawning threads / splitting work per std::thread::available_parallelism. I think the proposals here don't help for that case since I typically want the semantic equivalent of Result<NonZero>::unwrap_or(1) in that case. I think that further suggests that there's not necessarily a universal target for the into() to go to; I could see us wanting {Result,Option}<NonZero<T>> -> {Result,Option}<T> conversion instead, which makes it nicer to write e.g. std::thread::available_parallelism().into().unwrap_or(1). Though I'm not sure if the type inference works out for that case.

  7. wyfo commented on Aug 30, 2026

    @wyfo
    Author

    On the implementation side, I've definitely sometimes thought it silly to write out a match to do things like this when I know it's just a transmute, but the value of safety outweighs it. So one nice thing about having this method is that it'd be a place to do that transmute once but let other safe code take advantage of it

    The match is optimized away starting from opt-level=1, but a transmute is still better for debug build, and surely faster to compile on release build, so I support the idea.

    I think if I saw .unwrap_or_zero() my first reaction would be "why did they bother adding that when .unwrap_or(0) exists?" so giving it a more punchy name without needing the scary "unwrap" could help emphasize the point more.

    I think unwrap_or_zero is a bad name, because with all the other unwrap_or_xxx methods the output type is the wrapped type. With or_null/ or_zero, the output type is no longer the wrapped type, so it's not an unwrapping.

  8. clarfonthey commented on Aug 30, 2026

    @clarfonthey
    Contributor

    To be clear for the NonZero case, it explicitly has to use a transmute since the NullablePrimitive API actually doesn't have a way of getting a zero value. But I agree that in general, matches are more readable. NonNull seems to also just use branches everywhere since I think there's a few weird issues with transmuting between DSTs, which are sometimes possible here.

  9. Zorbatron commented on Sep 21, 2026

    @Zorbatron

    I found this ACP when I came up with a similar idea and thought I'd mention it here before opening a new issue.

    Could this be extended to Option<&T> and Option<&mut T>?
    Something like:

    impl<T> Option<&T> {
        fn name_tbd(self) -> *const T {
            match self {
                None => core::ptr::null(),
                Some(inner) => inner as *const T,
            }
        }
    }
    
    impl<T> Option<&mut T> {
        fn name_tbd(self) -> *mut T {
            match self {
                None => core::ptr::null_mut(),
                Some(inner) => inner as *mut T,
            }
        }
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions