@WG-prioritization/alerts issue #77912 has been requested for prioritization.
I-nominated
?Issue #77912's prioritization request has been removed.
Apparently this is intended behavior :P
yeah, it might make sense to ask in #t-lang if there are any objections to inferring lifetimes to 'static
if all function inputs do not contain any lifetimes
because at least for me that seems like a fine extension of the current rules (edit: nm I don't think so anymore)
one issue are things like
fn test(v: *const u32) -> &u32 {
unsafe { &* v }
}
Or maybe we could desugar to:
fn foo<'a>() -> &'a i32 {
&0
}
?
personally I'm ok with it, I occasionally run into the opposite issue actually, when I have fn f(&T) -> &U
and &U
can be *'static
but it's infered to the lifetime of &T
yeah, that has the same issue for pointers though
Maybe we only desugar if the return value does not use a raw pointer?
Joshua Nelson said:
personally I'm ok with it, I occasionally run into the opposite issue actually, when I have
fn f(&T) -> &U
and&U
can be*'static
but it's infered to the lifetime of&T
also had that a few times myself, but these cases should always be harmless as 'static
is always longer than the lt of &T
Camelid said:
Maybe we only desugar if the return value does not use a raw pointer?
I feel pretty strongly function types should not depend on the function body
this has caused so much trouble for rustdoc
this is the reason for https://github.com/rust-lang/rust/issues/75100
please don't, as this is relevant for program safety I really feel like we shouldn't use any unintuitive rules here
I guess it just feels confusing to me that the first works and the second does not:
fn foo(s: &i32) -> &i32 { &0 }
fn bar() -> &i32 { &0 }
this would mean that https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/subst/struct.GenericArg.html would be a landmine if it didn't contain PhantomData
as it uses a NonZeroUsize
instead of a pointer
That's beyond my Rust knowledge :smile:
Is there any way to make my example less confusing? If foo
used s
in the return value, it would make sense, but having the addition of an unused argument change whether the code compiles is just confusing to me.
Could someone move this to #t-lang?
it's lifetime elision
I know, it's just confusing to me that it's applied in one case but not another.
Likewise, that A works, but B does not:
fn foo(s: &i32) -> &i32 { &0 } // A
fn foo(s: i32) -> &i32 { &0 } // B
I do agree that this can be confusing
I guess it makes sense from the viewpoint of the compiler, but as a user it is very unintuitive
well it's meant to make writing functions easier
imagine if you hade to write fn f<'a>(&'a str) -> &'a str{
everywhere
instead of fn f(&str) -> &str
I like it, I just want it to be more general
the rule is pretty much: "assign each elided lt in the function args a unique lt param, if there is exactly one elided lifetime there use that one for the return argument"
Is that rule in the docs?
It's helpful :)
it might make sense to move this to the actual rust book
A lot of stuff in the nomicon seems like it belongs in the book :)
but as the only error case here is: user gets a fairly helpful compiler error to manually add lt params and now uses more than necessary
i don't think that it's worth it
Camelid said:
A lot of stuff in the nomicon seems like it belongs in the book :)
the more info you put into a summary the less of a summary it is
lcnr said:
one issue are things like
fn test(v: *const u32) -> &u32 { unsafe { &* v } }
I don't quite understand your concern. What stops someone from doing this?
fn test(s: &i32, v: *const u32) -> &u32 {
unsafe { &* v }
}
It compiles fine, and has the exact same issue
considering that the goal of the introductory material is to get people to start using rust I personally prefer to remove everything that is not strictly necessary or explained by compiler errors
What stops someone from doing this?
that people write code like this less often
we may want to lint this case
By the way, what is the underlying issue with eliding lifetimes for this?
fn test(v: *const u32) -> &u32 {
unsafe { &* v }
}
yeah :laughing: do you want to open an issue for this in the rust repo?
Sure, feel free to edit the issue if I get anything wrong :)
By the way, what is the underlying issue with eliding lifetimes for this?
fn main() {
let z;
{
let x = 7;
let y: *const u32 = &x;
z = test(y);
}
}
(test
has to unsafe anyways, as we could always call it with 0 as *const u32
but that's beside the point rn)
When casting pointers to references you have to be very careful with the lifetime of this reference to prevent use after frees
very often it is instead helpful to "remember" the correct lifetime of pointers by using a newtype with PhantomData
struct WeirdPtr<'a> {
ptr: *const u32,
_marker: PhantomData<&'a u32>,
}
Opened #77914
lcnr said:
very often it is instead helpful to "remember" the correct lifetime of pointers by using a newtype with
PhantomData
struct WeirdPtr<'a> { ptr: *const u32, _marker: PhantomData<&'a u32>, }
Huh, interesting. I've never quite understood PhantomData
. I think I'll go and read up on it so I finally understand it :)
Why would you use this special wrapper instead of just using &'a T
?
&T must always be valid for reads
*const T can be invalid as long as you don't dereference it
But why would you want to pass around an invalid ptr?
I guess I need to do more low-level Rust stuff :)
you can also use this to store some data in the lower bits of the pointer in case the pointee has an alignment greater than 1
this is what GenericArg
does
Wow, way over my head :laughing:
Oh, I think I've read about that before
So this
struct WeirdPtr<'a> {
ptr: *const u32,
_marker: PhantomData<&'a u32>,
}
is an optimization to store a reference and some data?
it could be
it's a pointer that does not have a restrict dereferencable inbounds
LLVM attribute
it could also be a shared reference which might get written to somewhere else
lcnr said:
it could also be a shared reference which might get written to somewhere else
What do you mean?
*const u32
does not have any aliasing restrictions
so you can mutate the value pointed to by a *const u32
without causing undefined behavior
well
I think you mean *mut T
?
both
Huh, so is the const
part just a note saying "this shouldn't be mutated, but can be"?
there is close to no difference between *const T
and *mut T
except for variance
A new term to look up :wink:
yeah, *const T
is pretty much only right if the pointee is only meant to be read
It's historically there for documentation purposes mainly, in practice it's for variance too
at least in my experience it is mostly for variance
*const T
is valid for an owning pointer too
it is so easy to shoot yourself in the foot when having a covariant mutable pointer
So you mean that *const T
and *mut T
are the same except that *mut T
can be passed to a function expecting *const T
but not vice versa?
@lcnr I mean that the usage in C code (where the terminology comes from) was for docs (memcpy, etc.)
Nowadays, in Rust, the usage is for variance indeed
no, you can go from *const &'static T
to *const &'a T
but not from a *mut &'static T
to *mut &'a T
@Camelid variance is the mechanic that allows to "shrink lifetimes" inside a type that would be generic over some lifetime parameter. Which seems like a harmless thing to do, but it isn't always the case: it is unsound to overwrite a long-lived borrow with a short-lived one. Since *mut
hints at being used for mutation, it is invariant / it doesn't allow lifetimes to shrink, whereas *const
allows it
For more info, see the nomicon chapter about it, or search in users.rust-lang.org for some detailed posts about the topic
lcnr said:
no, you can go from
*const &'static T
to*const &'a T
but not from a*mut &'static T
to*mut &'a T
Daniel Henry-Mantilla said:
Camelid variance is the mechanic that allows to "shrink lifetimes" inside a type that would be generic over some lifetime parameter. Which seems like a harmless thing to do, but it isn't always the case: it is unsound to overwrite a long-lived borrow with a short-lived one. Since
*mut
hints at being used for mutation, it is invariant / it doesn't allow lifetimes to shrink, whereas*const
allows it
That clears it up, thanks :)
Camelid said:
So you mean that
*const T
and*mut T
are the same except that*mut T
can be passed to a function expecting*const T
but not vice versa?
oh, that's actually news for me :laughing: *mut T
does decay to *const T
. Thanks :heart:
I had no idea, I was just guessing :laughter_tears: