Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

from_ref, from_mut: clarify documentation #125897

Merged
merged 3 commits into from
Jul 27, 2024
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions library/core/src/ptr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -777,8 +777,17 @@ where

/// Convert a reference to a raw pointer.
///
/// This is equivalent to `r as *const T`, but is a bit safer since it will never silently change
/// type or mutability, in particular if the code is refactored.
/// For `r: &T`, `from_ref(r)` is equivalent to `r as *const T`, but is a bit safer since it will
Copy link
Contributor

Choose a reason for hiding this comment

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

We shouldn't be leading off with this incorrect statement. I suggest we replcae "is equivalent" is "is often but not always equivalent".

/// never silently change type or mutability, in particular if the code is refactored.
Copy link
Contributor

Choose a reason for hiding this comment

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

In addition, I think it is worth also clarifying that for r: &mut T, from_ref(r) is equivalent to from_ref(r as &T) which is not guaranteed to be equivalent to r as *const and is not necessarily safer than r as const T, in particular if the resulting pointer will ever be cast to a *mut _ pointer. Maybe there is similar nuance for r: Deref<T>.

However, https://doc.rust-lang.org/reference/type-coercions.html#coercion-types does seem to say that when r: &mut T, r as *const T is equivalent to (r as &T) as *const T since there is no coercion rule for &mut T to *const T. So first we should decide whether we can really make the potentially-backward-incompatible change to add a &mut T to *const T coercion rule, because if we decide we can't, then this new wording isn't necessary.

Copy link
Contributor

Choose a reason for hiding this comment

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

for r: &mut T, r as *const T is equivalent to (r as &T) as *const T since there is no coercion rule for &mut T to *const T.

Actually, r as *const T could also be (r as *mut T) as *const T, so I guess some clarification is needed.

Copy link
Member Author

@RalfJung RalfJung Jun 3, 2024

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 is the right place to discuss the semantics of coercions. They behave the same everywhere, surely we don't want to explain them in each function that takes a reference. (e.g. slice.as_ptr() has exactly the same questions).

I also don't see why this PR should be blocked on #56604. That issue is long-standing so I don't expect it to be resolved soon, this clarification however is necessary short-term according to yourself as you said it is ambiguous.

then this new wording isn't necessary.

IMO the new wording is not necessary under any ruleset, since r is declared &T and so that's the only case the docs care about. As I already pointed out, for r of arbitrary type there are counterexamples to the equivalence. So the alternative interpretation would be something like "for all r where both expressions type-check, from_ref(r) is equivalent to r as *const T", which is quite far from what the text says and it needs to conjure a fresh T out of nothing -- IMO clear enough signals that this is not the intended interpretation.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's analogous to the situation for addr_of!, which has a similar caveat:

Note that Deref/Index coercions (and their mutable counterparts) are applied inside addr_of_mut! like everywhere else, in which case a reference is created to call Deref::deref or Index::index, respectively. The statements above only apply when no such coercions are applied.

In both cases, the construct is designed to make things safer, but not understanding the interaction with coercions is especially dangerous.

Copy link
Contributor

@briansmith briansmith Jun 3, 2024

Choose a reason for hiding this comment

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

I suggest adding the following text, which really gets to the point:

For r: &mut T, use from_mut(r).cast_const(), instead of from_ref(r), to get a *const T that can later safely be cast to *mut T.

///
/// The caller must ensure that the pointee outlives the pointer this function returns, or else it
/// will end up pointing to garbage.
Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest replacing "pointing to garbage" with "dangling". I also suggest adding here a concrete example from the clippy experience:

For example, as_ref(r) is not equivalent to r as *const T when lifetime extension is in effect:

let p = &foo() as *const T; // p is valid because of lifetime extension

to

let p = ptr::from_ref(&foo()); // Dangerous: p is dangling.

.

///
/// The caller must also ensure that the memory the pointer (non-transitively) points to is never
/// written to (except inside an `UnsafeCell`) using this pointer or any pointer derived from it. If
/// you need to mutate the pointee, use [`from_mut`]`. Specifically, to turn a mutable reference `m:
/// &mut T` into `*const T`, prefer `from_mut(m).cast_const()` to obtain a pointer that can later be
/// used for mutation.
#[inline(always)]
#[must_use]
#[stable(feature = "ptr_from_ref", since = "1.76.0")]
Expand All @@ -791,8 +800,11 @@ pub const fn from_ref<T: ?Sized>(r: &T) -> *const T {

/// Convert a mutable reference to a raw pointer.
///
/// This is equivalent to `r as *mut T`, but is a bit safer since it will never silently change
/// type or mutability, in particular if the code is refactored.
/// The caller must ensure that the pointee outlives the pointer this function returns, or else it
/// will end up pointing to garbage.
Copy link
Contributor

Choose a reason for hiding this comment

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

Same changes here: avoid "garbage" and also add an example of what to avoid.

Also, the structure of this comment should be made symmetric with the one for from_ref; i.e. move "The caller must ..." below the next paragraph.

///
/// For `r: &mut T`, `from_mut(r)` is equivalent to `r as *mut T`, but is a bit safer since it will
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here: scale back the "is equivalent to" claim.

/// never silently change type or mutability, in particular if the code is refactored.
#[inline(always)]
#[must_use]
#[stable(feature = "ptr_from_ref", since = "1.76.0")]
Expand Down
Loading