Repository navigation
Option<NonNull<T>>::as_ptr #872
Description
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
hanna-kruppe commentedon Aug 30, 2026
The name
as_ptris unfortunately closely related toas_refwhich means something else entirely onOption. I’m actually surprised we don’t already haveOption::as_ptras raw pointer equivalent ofOption::as_ref(I guess it’s not very useful in practice?).In the internals thread, @scottmcm suggested
or_zerofor theOption<NonZero<T>>->Tvariant and I like that. So maybe the NonNull related method should be calledor_null()? I guess one could also make an argument forunwrap_or_{zero,null}(although it does change the element type, unlike existingunwrap_or_*methods that go fromOption<T>toT).cuviper commentedon Aug 30, 2026
I like
or_nullandor_zerotoo.scottmcm commentedon Aug 30, 2026
I was pondering whether we ought to have both
.or_null()and.or_null_mut(). On the one hand, justor_nullreturning*mutis probably fine since it'll coerce to*constif needed, but the other hand, that argument could have applied forptr::null()too but that still hasptr::null_mut().I guess that since
NonNull::as_ptrreturns*mut, it's reasonable forOption<NonNull<_>>::or_nullto 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::newis 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 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).)clarfonthey commentedon Aug 30, 2026
So, was curious and opened rust-lang/rust#162029 to see if we could just add
Fromimpls for these.For the
NonZerocase (which I added myself, since I also use it a fair bit), I think it would be very natural to add afn get(self) -> TforOption<NonZero<T>>to mirror the same method onNonZeroitself. But forNonNull, that method would beas_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?Mark-Simulacrum commentedon Aug 30, 2026
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 ofResult<NonZero>::unwrap_or(1)in that case. I think that further suggests that there's not necessarily a universal target for theinto()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.wyfo commentedon Aug 30, 2026
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
unwrap_or_zerois a bad name, because with all the otherunwrap_or_xxxmethods the output type is the wrapped type. Withor_null/or_zero, the output type is no longer the wrapped type, so it's not an unwrapping.clarfonthey commentedon Aug 30, 2026
To be clear for the
NonZerocase, it explicitly has to use a transmute since theNullablePrimitiveAPI actually doesn't have a way of getting a zero value. But I agree that in general, matches are more readable.NonNullseems to also just use branches everywhere since I think there's a few weird issues with transmuting between DSTs, which are sometimes possible here.Zorbatron commentedon Sep 21, 2026
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>andOption<&mut T>?Something like: