Stream: t-compiler/wg-prioritization/alerts

Topic: I-prioritize #77912 Taking an argument hides lifetime error


triagebot (Oct 13 2020 at 19:19, on Zulip):

@WG-prioritization/alerts issue #77912 has been requested for prioritization.

Procedure

triagebot (Oct 13 2020 at 19:39, on Zulip):

Issue #77912's prioritization request has been removed.

Camelid (Oct 13 2020 at 19:39, on Zulip):

Apparently this is intended behavior :P

lcnr (Oct 13 2020 at 19:41, on Zulip):

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

lcnr (Oct 13 2020 at 19:41, on Zulip):

because at least for me that seems like a fine extension of the current rules (edit: nm I don't think so anymore)

lcnr (Oct 13 2020 at 19:42, on Zulip):

one issue are things like

fn test(v: *const u32) -> &u32 {
    unsafe { &* v }
}
Camelid (Oct 13 2020 at 19:42, on Zulip):

Or maybe we could desugar to:

fn foo<'a>() -> &'a i32 {
    &0
}

?

Joshua Nelson (Oct 13 2020 at 19:43, on Zulip):

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

lcnr (Oct 13 2020 at 19:43, on Zulip):

yeah, that has the same issue for pointers though

Camelid (Oct 13 2020 at 19:43, on Zulip):

Maybe we only desugar if the return value does not use a raw pointer?

lcnr (Oct 13 2020 at 19:43, on Zulip):

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

lcnr (Oct 13 2020 at 19:44, on Zulip):

Camelid said:

Maybe we only desugar if the return value does not use a raw pointer?

no no no no no

Joshua Nelson (Oct 13 2020 at 19:44, on Zulip):

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

Joshua Nelson (Oct 13 2020 at 19:44, on Zulip):

this has caused so much trouble for rustdoc

Joshua Nelson (Oct 13 2020 at 19:44, on Zulip):

this is the reason for https://github.com/rust-lang/rust/issues/75100

lcnr (Oct 13 2020 at 19:45, on Zulip):

please don't, as this is relevant for program safety I really feel like we shouldn't use any unintuitive rules here

Camelid (Oct 13 2020 at 19:45, on Zulip):

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 }
lcnr (Oct 13 2020 at 19:46, on Zulip):

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

lcnr (Oct 13 2020 at 19:46, on Zulip):

as it uses a NonZeroUsize instead of a pointer

Camelid (Oct 13 2020 at 19:46, on Zulip):

That's beyond my Rust knowledge :smile:

Camelid (Oct 13 2020 at 19:48, on Zulip):

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.

Camelid (Oct 13 2020 at 19:48, on Zulip):

Could someone move this to #t-lang?

Joshua Nelson (Oct 13 2020 at 19:48, on Zulip):

it's lifetime elision

Joshua Nelson (Oct 13 2020 at 19:48, on Zulip):

https://doc.rust-lang.org/nomicon/lifetime-elision.html

Camelid (Oct 13 2020 at 19:48, on Zulip):

I know, it's just confusing to me that it's applied in one case but not another.

Camelid (Oct 13 2020 at 19:49, on Zulip):

Likewise, that A works, but B does not:

fn foo(s: &i32) -> &i32 { &0 } // A
fn foo(s: i32) -> &i32 { &0 } // B
lcnr (Oct 13 2020 at 19:50, on Zulip):

I do agree that this can be confusing

Camelid (Oct 13 2020 at 19:50, on Zulip):

I guess it makes sense from the viewpoint of the compiler, but as a user it is very unintuitive

Joshua Nelson (Oct 13 2020 at 19:50, on Zulip):

well it's meant to make writing functions easier

Joshua Nelson (Oct 13 2020 at 19:50, on Zulip):

imagine if you hade to write fn f<'a>(&'a str) -> &'a str{ everywhere

Joshua Nelson (Oct 13 2020 at 19:50, on Zulip):

instead of fn f(&str) -> &str

Camelid (Oct 13 2020 at 19:51, on Zulip):

I like it, I just want it to be more general

lcnr (Oct 13 2020 at 19:51, on Zulip):

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"

Camelid (Oct 13 2020 at 19:52, on Zulip):

Is that rule in the docs?

Camelid (Oct 13 2020 at 19:52, on Zulip):

It's helpful :)

lcnr (Oct 13 2020 at 19:53, on Zulip):

it's here https://doc.rust-lang.org/nomicon/lifetime-elision.html#lifetime-elision

lcnr (Oct 13 2020 at 19:53, on Zulip):

it might make sense to move this to the actual rust book

Camelid (Oct 13 2020 at 19:53, on Zulip):

A lot of stuff in the nomicon seems like it belongs in the book :)

lcnr (Oct 13 2020 at 19:54, on Zulip):

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

lcnr (Oct 13 2020 at 19:54, on Zulip):

i don't think that it's worth it

lcnr (Oct 13 2020 at 19:55, on Zulip):

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

Camelid (Oct 13 2020 at 19:56, on Zulip):

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 }
}
Camelid (Oct 13 2020 at 19:56, on Zulip):

It compiles fine, and has the exact same issue

lcnr (Oct 13 2020 at 19:56, on Zulip):

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

lcnr (Oct 13 2020 at 19:56, on Zulip):

What stops someone from doing this?

that people write code like this less often

lcnr (Oct 13 2020 at 19:57, on Zulip):

we may want to lint this case

Camelid (Oct 13 2020 at 19:58, on Zulip):

By the way, what is the underlying issue with eliding lifetimes for this?

fn test(v: *const u32) -> &u32 {
    unsafe { &* v }
}
Camelid (Oct 13 2020 at 19:58, on Zulip):

lcnr said:

we may want to lint this case

The more the merrier! :)

lcnr (Oct 13 2020 at 19:58, on Zulip):

yeah :laughing: do you want to open an issue for this in the rust repo?

Camelid (Oct 13 2020 at 19:59, on Zulip):

Sure, feel free to edit the issue if I get anything wrong :)

lcnr (Oct 13 2020 at 19:59, on Zulip):

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);
    }
}
lcnr (Oct 13 2020 at 20:00, on Zulip):

(test has to unsafe anyways, as we could always call it with 0 as *const u32 but that's beside the point rn)

lcnr (Oct 13 2020 at 20:01, on Zulip):

When casting pointers to references you have to be very careful with the lifetime of this reference to prevent use after frees

lcnr (Oct 13 2020 at 20:02, on Zulip):

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>,
}
Camelid (Oct 13 2020 at 20:08, on Zulip):

Opened #77914

Camelid (Oct 13 2020 at 20:09, on Zulip):

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 :)

Camelid (Oct 13 2020 at 20:09, on Zulip):

Why would you use this special wrapper instead of just using &'a T?

Joshua Nelson (Oct 13 2020 at 20:09, on Zulip):

&T must always be valid for reads

Joshua Nelson (Oct 13 2020 at 20:10, on Zulip):

*const T can be invalid as long as you don't dereference it

Camelid (Oct 13 2020 at 20:10, on Zulip):

But why would you want to pass around an invalid ptr?

Camelid (Oct 13 2020 at 20:10, on Zulip):

I guess I need to do more low-level Rust stuff :)

lcnr (Oct 13 2020 at 20:11, on Zulip):

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

lcnr (Oct 13 2020 at 20:11, on Zulip):

this is what GenericArg does

Joshua Nelson (Oct 13 2020 at 20:11, on Zulip):

https://anniecherkaev.com/the-secret-life-of-nan

Camelid (Oct 13 2020 at 20:11, on Zulip):

Wow, way over my head :laughing:

Camelid (Oct 13 2020 at 20:12, on Zulip):

Oh, I think I've read about that before

Camelid (Oct 13 2020 at 20:13, on Zulip):

So this

struct WeirdPtr<'a> {
    ptr: *const u32,
    _marker: PhantomData<&'a u32>,
}

is an optimization to store a reference and some data?

lcnr (Oct 13 2020 at 20:13, on Zulip):

it could be

Joshua Nelson (Oct 13 2020 at 20:13, on Zulip):

it's a pointer that does not have a restrict dereferencable inbounds LLVM attribute

lcnr (Oct 13 2020 at 20:13, on Zulip):

it could also be a shared reference which might get written to somewhere else

Camelid (Oct 13 2020 at 20:13, on Zulip):

lcnr said:

it could also be a shared reference which might get written to somewhere else

What do you mean?

lcnr (Oct 13 2020 at 20:14, on Zulip):

*const u32 does not have any aliasing restrictions

lcnr (Oct 13 2020 at 20:14, on Zulip):

so you can mutate the value pointed to by a *const u32 without causing undefined behavior

Joshua Nelson (Oct 13 2020 at 20:15, on Zulip):

well

Joshua Nelson (Oct 13 2020 at 20:15, on Zulip):

I think you mean *mut T?

lcnr (Oct 13 2020 at 20:15, on Zulip):

both

Camelid (Oct 13 2020 at 20:15, on Zulip):

Huh, so is the const part just a note saying "this shouldn't be mutated, but can be"?

lcnr (Oct 13 2020 at 20:15, on Zulip):

there is close to no difference between *const T and *mut T except for variance

Camelid (Oct 13 2020 at 20:15, on Zulip):

A new term to look up :wink:

lcnr (Oct 13 2020 at 20:16, on Zulip):

yeah, *const T is pretty much only right if the pointee is only meant to be read

Daniel Henry-Mantilla (Oct 13 2020 at 20:16, on Zulip):

It's historically there for documentation purposes mainly, in practice it's for variance too

lcnr (Oct 13 2020 at 20:16, on Zulip):

at least in my experience it is mostly for variance

Daniel Henry-Mantilla (Oct 13 2020 at 20:16, on Zulip):

*const T is valid for an owning pointer too

lcnr (Oct 13 2020 at 20:17, on Zulip):

it is so easy to shoot yourself in the foot when having a covariant mutable pointer

Camelid (Oct 13 2020 at 20:17, on Zulip):

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?

Daniel Henry-Mantilla (Oct 13 2020 at 20:18, on Zulip):

@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

lcnr (Oct 13 2020 at 20:18, on Zulip):

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 (Oct 13 2020 at 20:21, on Zulip):

@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

Daniel Henry-Mantilla (Oct 13 2020 at 20:21, on Zulip):

For more info, see the nomicon chapter about it, or search in users.rust-lang.org for some detailed posts about the topic

lcnr (Oct 13 2020 at 20:24, on Zulip):

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

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6dfe4589c901ce2457553d3cd4e97117

Camelid (Oct 13 2020 at 20:25, on Zulip):

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 :)

lcnr (Oct 13 2020 at 20:26, on Zulip):

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:

Camelid (Oct 13 2020 at 22:36, on Zulip):

I had no idea, I was just guessing :laughter_tears:

Last update: Apr 11 2021 at 18:45UTC