-
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
Amend #1440: allow const
items to contain drop types.
#1817
Conversation
- Allow `const fn` to return types with destructors. | ||
- Disallow constant expressions which would result in the destructor being called (if the code were run at runtime). | ||
- Disallow constant expressions resulting in destructors being called at runtime (i.e: a `drop(foo)` in a `const fn`). |
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.
Doesn't the change proposed here explicitly allow for constant expressions that result in destructors being called at runtime?
Or do I misunderstand how the term "constant expression" or "runtime" is being used in this context?
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.
Oh, right, I prefer the original phrasing because it emphasizes that this is not about code ran at runtime, but rather some compile-time constant evaluation context. I'm not even sure we check this correctly with the feature flag enabled, it's a bit tricky if you can't rely on a full ban of drop types.
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.
Any further thoughts on this? Would love to get this in and implemented soon! |
Does anyone recall why precisely we prohibited them in the first place? I'm trying to bring that conversation back into cache. |
@@ -55,6 +57,8 @@ const fn sample(_v: Vec<u8>) -> usize { | |||
|
|||
Destructors do not run on `static` items (by design), so this can lead to unexpected behavior when a type's destructor has effects outside the program (e.g. a RAII temporary folder handle, which deletes the folder on drop). However, this can already happen using the `lazy_static` crate. | |||
|
|||
Destructors _will_ run on `const` items at runtime, which can lead to unexpected behavior when a type's destructor has effects outside the program. |
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'm not sure what this means. My mental model for a constant is that it is an "rvalue" -- in other words, each point where it is used, it is (roughly) "as if" you typed the expression in that place. Therefore, if we permit drop types there, I'd expect that Drop
will run at each point where the constant is referenced (and if it is never referenced, it will not run). I think we should expand this text to be much clearer about this point.
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've precisely identified the intent of the comment. I'll reword it.
@nikomatsakis It was trying to side-step the "drop runs on every copy which may be unexpected" fact, I believe, and it was quite narrow-scoped, focusing on |
Sorry for the delay. I've reworded to sketchy statement. |
Checking in on this. How's this looking in terms of getting accepted? |
- `static`s containing Drop-types will not run the destructor upon program/thread exit. | ||
- `const`s containing Drop-types _will_ run the destructor at the appropriate point in the program. |
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 that we should be more specific about what "the appropriate point" is. Probably by adding a subsection to allow us to elaborate. But when I started thinking about what to write I encountered a question. I'll post it on the main thread though.
OK, so, I was thinking more about this RFC. I just wanted to talk through what happens when we have a non-trivial constant. I'm not sure that there's a problem here, but I think the semantics are actually kind of subtle. Imagine I have this: const EMPTY_VEC: Vec<i32> = Vec::new(); Here I am assuming Now, if this were a static, when I reference But for a constant, we essentially get a new copy each time we access it. The way I imagine this really working is that we execute Now maybe by declaring |
@nagisa raises the question of what happens when you use such a constant in a pattern -- though I think the answer there is that we wouldn't really be instantiating the constant in that case, but rather doing a kind of comparison... so maybe drop doesn't have to run? But I'm not sure the best way to think about this. It seems tied in to the annoyance around |
Yes, this is also the case where I’ve wanted this: Regarding patterns, if fact that was the motivation for using Taking another step back, this whole thing would be a work around for privacy hygiene in |
@eddyb had a nice example of how you could easily misuse this capability: pub struct FileHandle { x: i32 }
impl Drop for FileHandle { /* closes the handle */ }
const STDIN: FileHandle = FileHandle { x: 0 }; // BAD Now any reference to STDIN will close file description 0. If you used a static, everything would be fine. This is not to say we should not permit drop in constants, but there is a kind of subtle "opt in" happening here. It'd be nice if we could do a bit better. @eddyb suggested perhaps a lint of some kind, but we'd have to tailor it properly. |
I feel that adding something that would then be limited against by default
is meaningless. Also nice example.
…On Feb 22, 2017 13:54, "Niko Matsakis" ***@***.***> wrote:
@eddyb <https://github.com/eddyb> had a nice example of how you could
easily misuse this capability:
pub struct FileHandle { x: i32 }impl Drop for FileHandle { /* closes the handle */ }const STDIN: FileHandle = FileHandle { x: 0 }; // BAD
Now any reference to STDIN will close file description 0. If you used a
static, everything would be fine.
This is not to say we should not permit drop in constants, but there is a
kind of subtle "opt in" happening here. It'd be nice if we could do a bit
better.
@eddyb <https://github.com/eddyb> suggested perhaps a lint of some kind,
but we'd have to tailor it properly.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#1817 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AApc0mFxPTG275Cazxpqwb117FTL29siks5rfCHYgaJpZM4LLp-p>
.
|
- Allow `const fn` to return types with destructors. | ||
- Disallow constant expressions which would result in the destructor being called (if the code were run at runtime). | ||
- Disallow constant expressions that require destructors to run during compile-time constant evaluation (i.e: a `drop(foo)` in a `const fn`). |
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.
doesn't this need further amending to be consistent with the other changes in the RFC?
(This probably ties into @nikomatsakis's earlier point that we need more specifics about what "the appropriate point in the program" is...)
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.
hmm well on further reflection I guess this text may be consistent as written. (Just need to get my head around when the destructors that are now allowed are actually getting invoked.)
@SergioBenitez @pnkfelix You might want to take a look at rust-lang/rust#40036 where I used a potential problem with this addition as an example. That is, we have to specify in what conditions drops wouldn't run in constant initializers (and thus when values of types with |
If we were going to lint against this, I think the right way would be to say that if a type implements @nagisa (I feel like this isn't quite the same as saying we "lint against it by default".) One could imagine calling the struct body "unsafe" as well. |
Summary of status and of comment thread(Note that this summary includes some discussion of const-evaluation semantics; in particular it includes my personal mental model for the semantics, which probably does not match the rest of the lang/compiler team...) The RFC proposes to loosen const-expressions so that they can have (sub)expressions of type T where T carries a destructor. (Destructor invocation continues to be disallowed at compile-time.) The comment thread has raised the question: "Why did const-expressions forbid destructors anyway?" One answer is that people may not expect the resulting semantics with multiple destructor invocations from a single const definition (more details in bulleted summary below). Niko notes in particular the distinction between As I understand it:
Given the caveats above, does the lang team want to take action now on this RFC? Or should we postpone until after resolving Issue 40036? |
This interacts poorly with associated constants and the future possibility of generic top-level constants. EDIT: As discussed on IRC, "MIR inlining" is a bit better than "macro-expansion" at describing this. |
After reflecting on this RFC, I figure that the capability for abuse is something that the designer of a type can readily defend against. My basis for this reasoning is: if you don't allow literals to be publicly constructed, and you don't provide a (It might be the case that we will want a lint for any |
@rfcbot fcp merge (see reasoning from my previous comment) |
Team member @pnkfelix 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. |
@rfcbot concern unresolved-question-for-lint
I agree, but my concern is mostly -- will they think to defend against it? This is why I am somewhat reluctant to go forward without a lint of the kind I described (specifically, one the struct literals). But I've also learned from experience that people hate "allowing" lints -- and this would be a lint that you basically have to allow, to declare that you know what you are doing, if you intend to have types (like Still, I'd like to ensure we revisit this question before stabilization, so I'm noting a formal concern that we should add an unresolved question about whether to add such a lint. |
I've spent some time mulling over this, and want to sketch my mental model of what's going on. There are types which are not, in general, However, I think there are several mitigating factors that make this perfectly OK:
Finally, if there does turn out to be a footgun here, we always have the option of linting our way out of it. @rfcbot reviewed |
@SergioBenitez, can you add the unresolved question @nikomatsakis mentioned above? I think that's effectively the only remaining blocker here. |
@rfcbot resolve unresolved-question-for-lint I've decided that this lint doesn't have to be a "formal unresolved question". We can just remember that it is an option if we feel like there is a real footgun evolving here. I tend to agree with @aturon that this probably won't arise in practice all that often. |
@nikomatsakis You have to write |
Ping @nikomatsakis -- I think your |
In the interest in making progress, I've manually applied the FCP tag. |
@rfcbot resolved unresolved-question-for-lint |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
The final comment period is now complete. |
Huzzah! This RFC amendment has been merged! The tracking issue is the one for the original RFC. |
At present, #1440 does not allow
const
items to contain drop types. This leads to somewhat surprising behavior, as illustrated in the following example:In the code above,
WORKS
typechecks whileFAILS
does not, even though they may appear to be identical to the user. The check fails because the compiler treatsTest
as an opaque type returned fromTest::new()
; becauseTest
contains aVec
, the type maydrop
, and so theconst
item is rejected under the current RFC. This PR changes the semantics to allow thedrop
type in theconst
item, thus typecheckingFAILS
.