-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
RFC: Unsafe Lifetime #3199
base: master
Are you sure you want to change the base?
RFC: Unsafe Lifetime #3199
Conversation
Bikeshedding: it might require a new edition to make this work fully, but why not just call it |
I considered 'unsafe, but it isn't actually unsafe to have one of these references and store it. Using it is what breaks down to unsafe. I picked ? Because it wouldn't require an edition boundary (I think), especially since the next one is 2024. Are there any reserved lifetime names that this feature could claim? |
As I said, I think we can get away with it working without an edition bump if we just require that it not be defined in the lifetime parameters; i.e. The main benefit of an edition bump is that it would become a compile error on the future edition to shadow the lifetime, just like how you can't define your own lifetime named Personally, I think that using a keyword would be a bit more clear, as the |
text/3199-unchecked-lifetime.md
Outdated
|
||
If you try to call a method whose arguments or return value include `'?`, that call will need to be wrapped in unsafe, because you are asserting that you know those references are valid despite the borrow checker not knowing. | ||
|
||
The addition of the `'?` lifetime also means the addition of two new reference types, `&'? T` and `&'? mut T`. These are in a sense halfway in between references and pointers. Dereferencing them is unsafe. Static references can be coerced into normal references, which can be coerced into unchecked-lifetime references, which can be coerced into raw pointers. The crucial difference between `&'? T` and `*const T` is that it is considered unsound for `&'? T` to be unaligned at any time, instead of only at the time of dereference for raw pointers. |
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 think it's worth also mentioning that it would also be non-null, even though NonNull<T>
exists to provide that for pointers.
The problem is you could have a function with an unchecked lifetime in the signature, that would collide with an existing function that names it's lifetime 'unchecked. |
see also #1918 (postponed) for the previous attempt at |
reading through this I don't see why it isn't the same as |
You can't use |
Turns out keyword names aren't allowed in lifetimes anyway, so |
I hadn't seen this rfc. I will go into some detail on this in prior art because it shares a lot with what I'm trying to do but I think this rfc is a little more precise in the approach. |
I'd assume niko's comment #1918 still applies today, so anything like this should be many years away. It's breaks abstraction boundaries if people instantiate foreign types' lifetime bounds with Instead, we'd need a default bound We'd need the libs team to figure out which core/std components should be changed from the default bound of We could plausibly treat |
This is the crucial difference between these two RFCs. |
Interesting, I'd missed this aspect, thanks. If I understand, |
Co-authored-by: Jacob Lifshay <[email protected]>
Co-authored-by: teor <[email protected]>
I'm not convinced that this is a good idea, for the following reasons:
To elaborate my last point: The only use case mentioned in the RFC are self-referential structs. If these are the main focus, then a |
The RFC says that This shouldn't have an effect on behavior because of rule 2, but I think it matters in how we justify the behavior of unsafe lifetimes.
This looks to me like it's making the transmute function a special case or you wouldn't be able to call it on something with an unsafe lifetime. If that is the case, then the RFC should mention this. If not, then I wonder what you mean by "function that is expecting a normal lifetime". |
I believe to make this work and be useful, it must be possible to opt into // any lifetime except 'unsafe:
fn foo<'a>(x: &'a i32) {}
// any lifetime, including 'unsafe:
fn bar<'a: 'unsafe>(x: &'a i32) {
// unsafe is needed here to dereference x!
}
foo::<'unsafe>(&42); // forbidden
bar::<'unsafe>(&42); // allowed
|
This is pretty similar to |
Going back to @burdges comment here:
In terms of the expressive power that this brings to the type system, what is missing from the taxonomy of pointers is a guaranteed-aligned pointer type without a lifetime. As the last sentence of this RFC mentions, that would be quite useful for any data structure implementation or FFI code to declare at the type level that pointer is "just" aligned (and maybe also non-null). I can see benefit from the restricted form |
for |
I had originally posted these concerns on the Zulip, but that conversation has died down so I'll repost here
To be clear: Enforcing that lifetime generics in scope for functions is enough, as far as I can tell, to ensure the continued soundness of any existing code. What is not clear at all is how this should work in a way that doesn't lead the trait solver to make incorrect deductions. Maybe the particular example above can be fixed by deciding that either the " I do think there's genuinely a good idea here, and that this kind of type would be useful even outside of unsafe code, but the right process would probably be to think more about the motivation and use cases, and then file a lang MCP so that the work to design the resulting type system correctly can be put in. |
I think this can be used to address @JakobDegen 's concerns, as well as clarify the discrepancy between lifetime parameters' bounds in impls/fns vs structs/enums. This also gets around the backwards compatibility requirement that the lifetime uses a keyword name. add one new lifetime and two new lifetime bounds:
the reverse is the case for types:
To avoid naming collisions, A notable name suggestion from Zulip is |
@maboesanman what you are describing there is, to me at least, not new, and this is how I had been interpreting things already. (We may decide later that we don't actually want to allow people to specify non-default constraints, but that's a separate issue). The example I posted still has issues, since it shows one (but not all) ways to turn a lifetime generic on a type into a lifetime generic on an impl. |
@JakobDegen the type of But your example proves that both struct and impl lifetime parameters need to be Maybe the struct/enum explicit bound can be removed on an edition bump? |
At this point, I'd suspect Arguably |
@burdges If declared lifetime parameters are always I think *aligned const is a reasonable feature but that doesn't do anything to enable storage of values which are generic over lifetimes that can't be understood by the borrow checker. |
But doesn't this explicitly contradict the example given in the motivation, since now this no longer works as a solution unless the problem types explicitly opt in (in which case they might as well just write a pointer based replacement?)
I believe this is technically compatible, but it is a massively breaking change, and really not in the spirit of edition changes, because the |
It does. I hadn't considered that example and you're right. However I think there's still value in adding this for cases where you want to a type you've made in both a self referential and normal context.
I think it would only need to add the bound in the case where you rely on |
If the main motivating example here is self-referential types, are there cases where a hypothetical |
@maboesanman possibly, but now you've secretly changed that adding an associated type as a field is a breaking change. That seems no good |
Your main issue is that the generic Wouldn't a way to extract the lifetime from the generic solve your problem? Is this what you mean in your |
I view this as similar to breaking a contract by adding a |
Sorry, should have been more clear. This being a thing at all is ok (I suppose), but changing this from not being a thing to being a thing, for existing code, is a pretty big surprise. I'm not sure that's such a good idea. |
Any updates on this? |
may we reuse parts of this for our own RFC? |
Of course! My takeaway from this rfc was that I had a somewhat incomplete understanding of some important concepts, but I still think there is some value in the ideas here. If you can extract that value that would be great! |
uh we mean... yeah, we do think your proposal is valuable, tho we haven't really been able to do much with it other than point out how it's unsound... >.< we did take some syntactic inspiration from it tho, if that counts for anything. but we're going a completely different direction. hmm, tho now that we think of it, we haven't considered interactions with (generic) associated types for our proposal... |
Triaging: A T-lang experiment with a closely-related alternative has been started: rust-lang/rust#130516 While the design doc is not a complete RFC, it mentions this design as an alternative:
@maboesanman Based on your remarks here, may I assume that you do not intend to incorporate the feedback or alternatives and advance this further?
|
@workingjubilee yes, upon reflection I don't think I have a robust enough understanding of the subtle and widespread implications of such a change to champion this. |
Introduce a new special lifetime
'unsafe
which is outlived by all other lifetimes. Using a type through a 'unsafe reference, or which is instantiated with an'unsafe
lifetime parameter is rarely possible without unsafe.RENDERED