Description
Right now we distinguish between "constant initializer" (static
, const
, promoted fragments) and "runtime" MIR, in that all locals in the former are 'static
and drops on them are ignored.
While this seems to work fine for the moment, it can't model, e.g. (mutable) local variables in "constant initializers" and may not be backwards-compatible with a model that can, if we stabilize any extensions.
Problematic example
If we accept rust-lang/rfcs#1817, the current implementation would allow both of these:
const FOO: i32 = (HasDrop {...}, 0).1;
const BAR: &'static i32 = &(HasDrop {...}, 0).1;
If we go by the rest of the language, FOO
should drop the tuple after copying out its second field, whereas BAR
should borrow the tuple forever, and happen to be pointing at the second tuple (resulting in no drop).
This is important even if we can't run destructors at compile-time, in order to error for FOO
but not BAR
.
So with the rules inferred above, we could desugar the two constants as follows:
const FOO: i32 = { let tmp = (HasDrop {...}, 0); tmp.1 /* drop(tmp) */ };
static TMP: (HasDrop, i32) = (HasDrop {...}, 0);
const BAR: &'static i32 = &TMP.1;
The distinction between the tmp
local slot and the TMP
static slot, and the lack of a drop for the latter, is what MIR is missing currently.
Proposed solution
@nikomatsakis and I have mulled this over, and the resulting plan is roughly as follows:
- use the existing rvalue scope rules that give the two borrowed temporaries in
&f(&g())
different scopes (with the inner one living only for the duration of the outer call), with the outermost scope of a constant initializer being special: no destructors run, and its lifetime is'static
- this breaks some (unstable)
const fn
uses, e.g.id(&0)
(recovered through rvalue promotion)
- this breaks some (unstable)
- for
'static
scopes, MIR building would use a different kind of "slot" and not emit a drop- we might want to repurpose&rename
Local
toSlot
- we might want to repurpose&rename
- MIR rvalue promotion would create such
'static
"slots" for all borrows, e.g.:
// promoted MIR fragment for `&[&42]`
static0 = 42;
tmp0 = &static0;
static1 = [tmp0];
return = &static1;
- MIR borrowck doesn't require any special treatment, and it should be able to detect malformed MIR
- e.g. a drop of a
'static
slot that was borrowed would overlap with the'static
borrow
- e.g. a drop of a
- in miri, only allow pointers into
'static
slots to "escape" the constant initializer evaluation- eventually we could also allow pointers into allocations that were "leaked", e.g. because their owner was placed in a
'static
slot:const S: &'static str = &X.to_string();
- eventually we could also allow pointers into allocations that were "leaked", e.g. because their owner was placed in a