-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Remove T: Sized
on pointer as_ref()
and as_mut()
#44932
Conversation
`NonZero::is_zero()` was already casting all pointers to thin `*mut u8` to check for null. It seems reasonable to apply that for `is_null()` in general, and then unsized fat pointers can also be used with `as_ref()` and `as_mut()` to get fat references.
(rust_highfive has picked a reviewer for you, use r? to override) |
src/libcore/tests/ptr.rs
Outdated
assert!(!mz.is_null()); | ||
|
||
unsafe { | ||
let ncs: *const [u8] = slice::from_raw_parts(null(), 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is UB (null in a &[u8]
) so unfortunately can begin to fail when the compiler gets smarter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any better way to get a null unsized pointer? Or are they impossible to be null in practice? If the latter, we could just drop those parts of the tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose I could implement a local Repr
hack for the tests, similar to slice::from_raw_parts
but without ever calling it a &
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Depending on how much you hate transmute
, you could do let ncs: *const [u8] = unsafe { mem::transmute((0usize, 0usize)) };
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went ahead and added a transmute
with Repr
, which I think is a little safer than a tuple because it can be #[repr(C)]
. This is just for testing anyway, so whatever we do isn't going to affect anyone in the wild.
@bluss also pointed out that it could be surprising that there are many possible null values for fat pointers. I added docs mentioning this, but if that's too weird, we could back off to just unsized |
To do the null pointer right (via @eddyb): |
Looks great to me! Since this'll be instantly stable, let's.... @rfcbot fcp merge |
Team member @alexcrichton has proposed to merge this. The next step is review by the rest of the tagged teams: Concerns: Once these reviewers reach consensus, this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
src/libcore/tests/ptr.rs
Outdated
pub len: usize, | ||
} | ||
|
||
mem::transmute(Repr { data: null_mut::<T>(), len: 0 }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need this: you can just cast null_mut::<[T; 0]>()
to *mut [T]
.
EDIT: ah yes I see that @bluss commented about this already.
OK, it now uses coercion to get the null pointers, and I added trait objects this way too. |
This is not the behavior that Go has for comparing interfaces (read: traits) against nil (read: null). Are we sure the behavior in this PR is useful? How does Go justify their behavior and do they regret it? The package main
import "fmt"
type S struct {}
func (*S) String() string {
return "S"
}
func main() {
var s fmt.Stringer = (*S)(nil)
fmt.Println(s == nil) // false
} @rfcbot concern go |
@dtolnay Currently it is UB to have a null vtable pointer, it's as if |
@dtolnay Note however that this isn't changing I don't really know Go. If you have such a |
Re: @cuviper
By design, it is not easy to tell if the data pointer is nil. This can be confusing but it is because interfaces with a nil data pointer are totally fine to call interface methods on -- as long as those methods do not lead to the nil pointer being dereferenced. In the code above, calling If checking the data pointer for nil is really what you mean, there are some ways. But as I understand it, this would be very unusual in Go code (which is why I was startled to see us define // use type assertion to check for nil data pointer of a particular type
p, ok := s.(*S)
if ok {
isNil = p == nil
}
// use reflection to check for nil data pointer of any type
isNil = reflect.ValueOf(s).IsNil() Re: @eddyb
Do you have any sense of what the future holds here? In particular, I would be concerned about how this PR interacts with the following two potential changes:
In combination, these would put us in a state like Go where checking for null data pointer is almost never what you want and checking for null data pointer + null vtable is almost always what you want. Have there been any discussions that would rule out one or both of these from being pursued in the future? |
If Rust But even with the possibility of Slices are weird too. If we want to defer a decision on |
I think that can't happen.
I don't yet agree that this would be useful. Do you have a use case in mind where the best solution involves checking whether the data pointer of a trait object is null? It is possible that these are universally better served by some combination of not using trait objects, using |
If nothing else, I think |
I don't think we'll ever see distinct requirements for validity of metadata between safe and unsafe pointers if we get any kind of Custom DSTs, because of the high complexity associated with having it not be uniform and requiring potentially user-defined conversions for casts which are builtin now. As for |
This is what makes Go's choice of behavior for Transliterated into Rust it would be: struct S {}
impl Trait for S {
unsafe fn f(*const self) -> &'static str {
"S"
}
}
fn main() {
let s: *const Trait = ptr::null::<S>();
println!("{}", s.is_null()); // false, calling Trait::f is fine
let s: *const Trait = ptr::null::<Trait>();
println!("{}", s.is_null()); // true, calling Trait::f is not fine
} |
https://golang.org/doc/faq#nil_error Considering advice like "It's a good idea for functions that return errors always to use the error type in their signature (as we did above) rather than a concrete type such as *MyError, to help guarantee the error is created correctly.", I can imagine this being a design mistake. As for why it happens, it's because there is no explicit p == nil The types of comparison have to be identical, so an implicit cast is inserted p == interface{}(nil) An interface is pair of a type pointer and a value. As Essentially this makes it a comparison like the following (this is pseudo-Go). (*int, nil) == (nil, nil)
A type needs to be compared while comparing 2 interfaces, as Go does provide value types. For instance, the following program returns package main
import "fmt"
func main() {
fmt.Println(interface{}(int(2)) == interface{}(int(2)))
fmt.Println(interface{}(int(2)) == interface{}(uint(2)))
} Considering Rust doesn't allow To be precise, |
@xfix we agree that the |
The exact semantics of `is_null` on unsized pointers are still debatable, especially for trait objects. It may be legal to call `*mut self` methods on a trait object someday, as with Go interfaces, so `is_null` might need to validate the vtable pointer too. For `as_ref` and `as_mut`, we're assuming that you cannot have a non-null data pointer with a null vtable, so casting the unsized check is fine.
I pushed an update to restore |
My comment concerns the semantics not of pointers, but of borrows. I am saying that, because
|
@ExpHP Yes, it seems necessary to produce |
@ExpHP @bluss in #44932 (comment) I would expect the first case to produce Some with a null data pointer and a non-null vtable. By Go's logic, the fact that the caller passed a non-null pointer EDIT: disregard, this would not make sense |
@dtolnay so if I understand your responses correctly, you want the following to be on the table as well? (i.e. allow not just raw pointers, but also borrows to have null data): struct S {}
impl Trait for S {
// Notice: &self, not *const self
fn f(&self) -> &'static str {
"S"
}
}
fn main() {
// Notice: &Trait, not a raw pointer
let s: &'static Trait = unsafe { ptr::null::<S>().as_ref().unwrap() };
println!("{}", s.f());
} |
@ExpHP that code is stable already so changing it would not be on the table. I read your comments again and I find the point about @rfcbot fcp merge |
Team member @dtolnay has proposed to merge this. The next step is review by the rest of the tagged teams: No concerns currently listed. Once these reviewers reach consensus, this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
@BurntSushi ticky box here waiting for you! |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
@bors: r+ |
📌 Commit 604f049 has been approved by |
@bors: r+ oops... |
💡 This pull request was already approved, no need to approve it again.
|
📌 Commit 604f049 has been approved by |
Remove `T: Sized` on pointer `as_ref()` and `as_mut()` `NonZero::is_zero()` was already casting all pointers to thin `*mut u8` to check for null. The same test on unsized fat pointers can also be used with `as_ref()` and `as_mut()` to get fat references. (This PR formerly changed `is_null()` too, but checking just the data pointer is not obviously correct for trait objects, especially if `*const self` sorts of methods are ever allowed.)
☀️ Test successful - status-appveyor, status-travis |
Remove `T: Sized` on `ptr::is_null()` Originally from #44932 -- this is purely a revert of the last commit of that PR, which was removing some changes from the previous commits in the PR. So a revert of a revert means this is code written by @cuviper! @mikeyhew makes a compelling case in rust-lang/rfcs#433 (comment) for why this is the right way to implement `is_null` for trait objects. And the behavior for slices makes sense to me as well. ```diff impl<T: ?Sized> *const T { - pub fn is_null(self) -> bool where T: Sized; + pub fn is_null(self) -> bool; } impl<T: ?Sized> *mut T { - pub fn is_null(self) -> bool where T: Sized; + pub fn is_null(self) -> bool; }
NonZero::is_zero()
was already casting all pointers to thin*mut u8
to check for null. The same test on unsized fat pointers can also be used withas_ref()
andas_mut()
to get fat references.(This PR formerly changed
is_null()
too, but checking just the data pointer is not obviously correct for trait objects, especially if*const self
sorts of methods are ever allowed.)