-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Forget
marker trait
#3782
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
base: master
Are you sure you want to change the base?
Forget
marker trait
#3782
Conversation
This definitely seems to be a reasonably motivated RFC, although it's going to definitely be something I'll have to read through a lot more closely to fully comment on it. A few first impressions:
Again, I do want to go through this a bit more closely before fully commenting on it, but I think that you definitely need to go back and make the primary motivation for this crystal clear: because of Generally, the only way to deal with this is to make these calls unsafe and tell people to pinky-promise they run the future to completion, but it would be nice to be able to have a safe way to do this instead. |
@clarfonthey We do use verbs as trait name when the only purpose of the trait is to support that verb, think of |
This is definitely a much desired feature that would enable new safe design patterns in Rust. Having a gradual migration path with I appreciate all the links to the prior art. |
# Guide-level explanation | ||
[guide-level-explanation]: #guide-level-explanation | ||
|
||
The core goal of `Forget` trait, as proposed in that RFC, is to bring back the "Proxy Guard" idiom for non-static types, with `async` being the primary motivation. |
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.
Do I understand correctly that all !Forget
types need to have a lifetime bound to have any effect? Does it ever make sense to have a !Forget
type that has no lifetime? (like an integer)
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.
That’s correct. Non-Forget
types must be dropped before their lifetime ends
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.
From the perspective of use cases and definitions, it really makes little sense to have !Forget
and 'static
together. But I am not opposed too, it may have some other benefits which I am not aware of / or implementational / migrational benefits.
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 makes me wonder whether PhantomNotForget
should have a generic lifetime argument. Although it's not technically necessary, it would make it clearer that it's about lifetimes. I could work as self-documenting code which lifetime is the tricky one, and PhantomNotForget<'static>
would be something to lint against.
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.
Yeah, I also think so, but I fear some decision makers may not like it
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.
If there were to be a blanket impl<T: 'static> Forget for T
, then the lifetime parameter would be necessary for the type to work at all. I think that alone justifies it. IIUC the lifetime parameter would have to be covariant
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 would mention it just in case, this is already in unresolved questions:
Maybe force impl Forget for T where T: 'static {} and add a generic to the PhantomNonForget? Use cases and unsafe guarantee are fine with it, and we already allow !Forget in static.
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 makes me wonder whether
PhantomNotForget
should have a generic lifetime argument. Although it's not technically necessary, it would make it clearer that it's about lifetimes. I could work as self-documenting code which lifetime is the tricky one, andPhantomNotForget<'static>
would be something to lint against.
In my (not new) reference implementation of the Forget
trait, I have added it on a wrapper type Unforget
as a contravariant lifetime:
https://zetanumbers.github.io/leak-playground/leak_playground_std/marker/struct.Unforget.html
As to the magic 'static
lifetime, seems like it made a lot of contention in its prior discussion:
#t-lang > The destruction guarantee and linear types formulation
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
Co-authored-by: Orion "Ardi" Gonzalez <[email protected]>
Co-authored-by: Yosh <[email protected]>
This comment was marked as resolved.
This comment was marked as resolved.
Great. Then you can find an explanation from @kornelski or me under that comment you linked. |
It may be confusing + we may potentially teach `Pin` in terms of `Forget` then the other way around.
Co-authored-by: 스도리 <[email protected]>
Please note that the single case of “what if” you commented on earlier occurs in the guide-level explanation, which is intended to provide an intuitive understanding of the feature. From the rust-lang/rfcs repository:
The section you commented on explained concretely: there should be a borrow-checked lifetime between |
Doesn't change what I've said, nothing new.
Discussed in review. |
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.
There are major open problems, which do not allow this and default bounds RFC to be an effective proposal.
Considering I couldn't find anything in the reference-level explanation closely related to the explanation on why rendezvous channels could not operate on unforgettable types, let me exactly describe why rendezvous channels create a major hole in the current design. Let's start by exploring the new JoinGuard design. You could restrict Just like async drop, unforgettable types were intended to enable safe borrowing async tasks as part of an idea of the structured concurrency. Composition of borrowing async tasks means being able to hold one such task handle inside of another (borrowing) async task, and analogously designed non-thread-safe
It is clear to a library developer that any synchronization channel with a buffer (for example mpsc) could easily create a self-reference by sending a receiver object to its own channel buffer, as such What you are actually trying prohibit is the transfer of (any) Is what I'm talking about clear now? If this issue with rendezvous channels wasn't there I would have already published this RFC. For now unforgettable types proposal have only been discussed on the rust zulip. Here's a thread about this exact problem: #wg-async > Structured parallel tasks require !Send in Send coroutines EDIT: If you think your RFC have already addressing this concern, please leave a citation. |
Do we have a misunderstanding? You absolutely can and should send If your question is "your reasoning is incomplete because you ban The text later was written before I spot this misunderstanding, but I think it still should be relevant. I do not enjoy long scrolls, so I would put in the Long response to the previous commentThe reason this RFC is published by me is because I fundamentally disagree with you on that point, and what is new. Moving The most important thing is the cost-benefit ratio. Benefits are immense, motivation is huge. From the side of the costs, you don't loose anything by requiring And please note, we are not talking about structured concurrency alone in this RFC, there are plenty of other use cases. What I am trying to say in this RFC is that everything should be connected via lifetime - ergonomics is still at the highest level, but borrow checker is able to control you code better.
The fact that you can exploit that unsoundness by sending To give more intuition: disconnected To reiterate, the requirement to have a lifetime between If your question is "Why require lifetime between You certainly do have a grasp on this proposal. I would truly appreciate if you would be open-minded and instead of purely destructive actions (for now you just brought up some cases and speculations where you personally believe RFC should be discarded, without considering const-benefit ratio) put some work into improving the RFC. For example, looking for additional costs, investigating how you can use safe code to create unsoundness (which happens from time to time even in the stable Rust due to human factor), or thinking about real use cases to decide on I'm sorry if I've offended you in any way or if you think I've stolen your credits - I'm not claiming anything, I just believe that you're not correct and I'm willing to spend the time and effort to improve the language. If you feel like I answered your concerns, please mark your comments as resolved, thank you. |
After considering this for a bit, I don’t think category 2 makes sense. If you have an owned value (on the stack or in a |
If the value has to be pinned in particular place, than using |
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 seems like a useful change, but it would be easier to understand with some tweaks to the ordering of paragraphs, and some wording clarifications.
Co-authored-by: teor <[email protected]>
Co-authored-by: teor <[email protected]>
Co-authored-by: teor <[email protected]>
Co-authored-by: teor <[email protected]>
Co-authored-by: teor <[email protected]>
e6a737b
to
e534b0c
Compare
3aef99f
to
073b9ec
Compare
073b9ec
to
555fbfa
Compare
…f borrow-checker to prevent cycles and leaks
Add a
Forget
marker trait indicating whether it is safe to skip the destructor before the value of a type exits the scope and basic utilities to work with!Forget
types. Introduce a seamless migration route for the standard library and ecosystem.Rendered
Pre-RFC thread
Unresolved questions
!Forget
gives to theunsafe
code is already fulfilled for all'static
types. Because of that, it doesn't really make a lot of sense to have!Forget + 'static
, it is still sound tomem::forget
that type. Should we forceimpl<T: 'static> Forget for T
then? Won't that impl create some unexpected problems?