Skip to content

Relax safety documentation of FnPtr::from_ptr(). - #163821

Open
kpreid wants to merge 1 commit into
rust-lang:mainfrom
kpreid:fnptr
Open

kpreid wants to merge 1 commit into
rust-lang:mainfrom
kpreid:fnptr

Conversation

@kpreid

@kpreid kpreid commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

View all comments

(These changes are all intended to be “it obviously should be this”, not “let’s choose to allow this”. If we don’t want to allow one of these things, I will be happy to revise this text to be stricter.)

Tracking issue for fn_static: #148768

@rustbot label +A-docs +T-opsem +T-libs

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Oct 5, 2026
@rustbot

rustbot commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator

r? @clarfonthey

rustbot has assigned @clarfonthey.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: libs
  • libs expanded to 12 candidates
  • Random selection from JohnTitor, Mark-Simulacrum, clarfonthey

@rustbot rustbot added A-docs Area: Documentation for any part of the project, including the compiler, standard library, and tools T-opsem Relevant to the opsem team labels Oct 5, 2026
Comment thread library/core/src/ops/function.rs Outdated
@theemathas

Copy link
Copy Markdown
Contributor

cc @rust-lang/opsem, due to guaranteeing new things in user-facing documentation, although in an unstable API.

Comment thread library/core/src/ops/function.rs Outdated
Comment on lines +335 to +336
/// Returns the function pointer as a [`NonNull<Code>`], thus forgetting the signature and
/// everything else about the type except except the fact that it was a function pointer.

@asquared31415 asquared31415 Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we perhaps also link to the docs talking about the hazards of function pointers? In particular, NonNull<Code> implements PartialEq, but it has the same problems as "normal" function pointers fn() with regards to comparisons and identity.

(also the wrong lint fires when you try to equate NonNull<Code>, the wide pointer lint fires instead of the fn pointer comparison lint, but that should be fixed in a different PR)

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems fair. Potentially something like, in general, you should not expect to glean any useful information from this pointer besides converting it back into a function, and to link fn_addr_eq to explain pitfalls.

(whose docs I don't think have been updated yet, but will be)

Allow it to be used for FFI/linking applications and not just
Rust-to-Rust type erasure.
/// from an [`FnPtr::as_ptr`] call from a function
/// pointer type that is ABI compatible.
/// `ptr` must have been obtained from calling [`FnPtr::as_ptr`] on a function pointer type
/// that is ABI-compatible, or from an equivalent foreign operation (e.g. requesting a function

@RalfJung RalfJung Oct 7, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ABI-compatible with what?

View changes since the review

@RalfJung RalfJung Oct 7, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if this is correct. Self here may be e.g. fn(i32) -> i32, right? In that case the constructor needs to require that this is a safe function with that signature. Being ABI-compatible is not enough.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm not entirely sure this is correct either. It feels like we're back into the fn_addr_eq discussion again, where you really can't be sure that functions are compatible because the compiler might insert invisible nonsense in the prelude.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this has anything to do with fn_addr_eq.

It's just that the safety invariant for fn(i32) -> i32 is "when this function is invoked with an i32, it will run without causing UB and if it returns, the result is an i32". The comment that's here right now is not strong enough to justify this property.


/// Returns the function pointer as a [`NonNull<Code>`].
/// Returns the function pointer as a [`NonNull<Code>`], thus forgetting the signature and
/// everything else about the type except except the fact that it was a function pointer.

@RalfJung RalfJung Oct 7, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// everything else about the type except except the fact that it was a function pointer.
/// everything else about the function `self` points to.

NonNull<Code> can be safely constructed to point to non-functions so arguably this forgets everything.

View changes since the review

#[rustc_deny_explicit_impl]
#[rustc_dyn_incompatible_trait]
pub trait FnPtr: Copy {
/// Returns the address of the function pointer.

@RalfJung RalfJung Oct 7, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Returns the address of the function `self` points to.

View changes since the review

/// The function pointer must have been obtained
/// from an [`FnPtr::as_ptr`] call from a function
/// pointer type that is ABI compatible.
/// `ptr` must have been obtained from calling [`FnPtr::as_ptr`] on a function pointer type

@RalfJung RalfJung Oct 7, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// `ptr` must have been obtained from calling [`FnPtr::as_ptr`] on a function pointer type
/// `ptr` must have been obtained by calling [`FnPtr::as_ptr`] on a function pointer type

View changes since the review

@RalfJung

RalfJung commented Oct 7, 2026

Copy link
Copy Markdown
Member

cc @rust-lang/opsem, due to guaranteeing new things in user-facing documentation, although in an unstable API.

I am not sure what the opsem part of this is, this is mostly changing the docs on a trait whose invariant is library-defined, no?

@clarfonthey

clarfonthey commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

I was under the impression that the machinery of FnPtr is firmly in opsem territory even though the library is free to further restrict the API out of caution. So, effectively, the upper bound on guarantees is controlled by opsem, but that libs may choose to further lower that bound.

@RalfJung

RalfJung commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

When the trait is implemented for fn types, then from the opsem side any non-null value is fine for from_ptr. That's the validity invariant. But from a soundness side that's obviously not enough, the safety invariant is stronger than that. But safety invariants aren't really opsem any more, they are lang/types at least as much as they are opsem (in terms of who gets to decide about them). Though opsem folks tend to know about them so we can still help. :)

I have no idea which other types the trait is or will be implemented for.

@theemathas

theemathas commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

@RalfJung In the initial version of this PR, there was a new guarantee is that FnPtr::from_ptr() on any arbitrary non-null pointer is not immediately UB. I'm not sure if the current version has any new guarantees.

@clarfonthey

Copy link
Copy Markdown
Contributor

In the initial version of this PR, there was a new guarantee is that FnPtr::from_ptr() on any arbitrary non-null pointer is not immediately UB. I'm not sure if the current version has any new guarantees.

Yeah, from a libs perspective that's just incorrect. There is the strong boundary of language UB where the compiler is immediately allowed to eat your laundry, but in the standard library we like to make stronger guarantees so that safe code can't invoke UB. Even if it's not "immediately" UB, having a function pointer that cannot be called is a liability that we don't want to offer.

@clarfonthey

Copy link
Copy Markdown
Contributor

@rustbot author

(since there are still open questions)

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Oct 10, 2026
@rustbot

rustbot commented Oct 10, 2026

Copy link
Copy Markdown
Collaborator

Reminder, once the PR becomes ready for a review, use @rustbot ready.

@theemathas

theemathas commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

There is the strong boundary of language UB where the compiler is immediately allowed to eat your laundry, but in the standard library we like to make stronger guarantees so that safe code can't invoke UB.

@clarfonthey Turns out there's already precedent for making a distinction between "not immediately UB, but may cause UB later" and "all bets are immediately off" in the standard library. See #134598

@RalfJung

Copy link
Copy Markdown
Member

I don't see such a distinction there? That is just spelling out more carefully what may or may not later be UB.

@clarfonthey

Copy link
Copy Markdown
Contributor

The precedent I was referring to is specifically in the assert_unsafe_precondition macro and we're choosing to loosen the requirements in that location specifically after a long and arduous debate. I don't think we should do the same here.

@RalfJung

RalfJung commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

It's long discussion but assert_unsafe_precondition does not seem to be mentioned anywhere in the PR or the diff. So I have no idea what you are referring to.

@clarfonthey

Copy link
Copy Markdown
Contributor

I was referring to the context of the quote @theemathas made:

There is the strong boundary of language UB where the compiler is immediately allowed to eat your laundry, but in the standard library we like to make stronger guarantees so that safe code can't invoke UB.

This was me referring to the current distinction which is most well codified in the assert_unsafe_precondition macro, between language and library UB. Language UB checks are turned off for miri (which does its own checking) but library UB checks are still turned on in all cases.

@RalfJung

Copy link
Copy Markdown
Member

Turns out there's already precedent for making a distinction between "not immediately UB, but may cause UB later" and "all bets are immediately off" in the standard library. See #134598

So I still don't understand what the PR has to do with that but &str is an example of that, where we document that UTF-8 is a library/safety invariant, not a validity invariant. When working with Pin it is also quite common to have &mut references that are not actually satisfying their full safety invariant. There is a number of situations in Rust where one may have to hold a value that's "tainted" by having some of its key properties broken, but a few key operations are still possible in this "tainted" state. We are currently terrible at documenting this, but it's not something we can just pretend won't happen.

@clarfonthey

clarfonthey commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Right, and I guess my main issue communicating here is that I don't think that we should perform the exception here, where the distinction between when UB happens should be part of a guarantee.

In general, the library team likes to guarantee as few possible exceptions to invariants as possible, and str is one case where these exceptions are useful. FnPtr is not only not one of those cases where it's useful to make this distinction, but additionally, there is the potential for just outright making this a case where immediate language UB occurs (e.g., niches requiring certain values) and thus libs wouldn't even have a say in that case. It's just always UB.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-docs Area: Documentation for any part of the project, including the compiler, standard library, and tools S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-libs Relevant to the library team, which will review and decide on the PR/issue. T-opsem Relevant to the opsem team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants