- Feature Name: dropck_parametricity
- Start Date: 2015-08-05
- RFC PR: rust-lang/rfcs#1238/
- Rust Issue: rust-lang/rust#28498
Summary
Revise the Drop Check (dropck
) part of Rust's staticcannot-assume-parametricity
and unguarded-escape-hatch
.
-
cannot-assume-parametricity
(CAP): Makedropck
analysis解析stop relying on parametricity of type-parameters. -
unguarded-escape-hatch
(UGEH): Add an attribute (with some name starting with "unsafe") that a library designer can attach to adrop
implementation実装that will allow許可する、可能にするa destructor to side-step thedropck
's constraints制約(unsafely).
Motivation
Background: Parametricity in dropck
The Drop Check rule (dropck
) for Sound Generic Drop relies on a reasoning process that needs to infer that the behaviorfn foo<T>
) does not depend on the concreteT
in fn foo<T>
), at least beyond the behavior
This property
-
Parametricity, in this context,
文脈、背景essentially says that the compiler can reason about the body offoo
(and the subroutines thatfoo
invokes) without having to think about the particular concrete具体的な/具象的なtypes that the type parameter仮引数T
is instantiated with.foo
cannot do anything with at: T
except:-
move
t
to some other owner expecting aT
or, -
drop
t
, running its destructor and freeing associated関連付けられたresources.
-
-
For example, this allows
許可する、可能にするthe compiler to deduce that even ifT
is instantiated with a concrete具体的な/具象的なtype like&Vec<u32>
, the body offoo
cannot actually read anyu32
data out of the vector. More details about this are available on the Sound Generic Drop RFC.
"Mistakes were made"
The parametricity-based reasoning in the Drop Check analysisdropck
) was clever, but fragile and unproven.
-
Regarding its fragility, it has been shown to have bugs; in particular, parametricity is a necessary but not sufficient condition
条件to justify the inferences thatdropck
makes. -
Regarding its unproven nature,
dropck
violated違反するthe heuristic in Rust's design設計(する)to not incorporate ideas unless those ideas had already been proven effective elsewhere.
These issues might alone provide motivation for ratcheting back on dropck
's rules in the short term,
However, there is also a specificdropck
analysis
Impl specialization is inherently non-parametric
The parametricity requirement in the Drop Check rule over-restricts the design
In particular, the impl specialization RFC describesf
to end up in different sequencesT
, even when T
has no trait boundsf
.
Detailed design設計(する)
Revise the Drop Check (dropck
) part of Rust's staticcannot-assume-parametricity
(CAP) and unguarded-escape-hatch
(UGEH).
Though the revisions are givendropck_parametricity
. (Note however that this might be irrelevant
cannot-assume-parametricity
The heart of CAP is this: make dropck
analysis
Changes to the Drop-Check Rule
The Drop-Check Rule (both in its original form'a
must strictly outlive some value v
, where v
owns data of type D
; the rule gave two circumstances where 'a
must strictly outlive the scope of v
.
-
The first circumstance (
D
is directly直接instantiated at'a
) remains unchanged by this RFC. -
The second circumstance (
D
has some type parameter仮引数with trait-provided methods, i.e. that could be invoked呼び出すwithinDrop
) is broadened by this RFC to simply say "D
has some type parameter."仮引数
That is, under the changes of this RFC, whether the type parameter&'a AlreadyDroppedData
.
cannot-assume-parametricity
is a breaking change, since the languagestruct
may read from data held in its C
parameter,fn drop
formerly appearedC
. This will causerustc
to reject code that it had previously accepted
CAP stabilization details
cannot-assume-parametricity
will be incorporated into the beta and stable Rust channels, to ensure
-
It is not yet clear whether it is feasible to include a warning cycle for CAP.
-
For now, this RFC is proposing to remove the parts of Drop-Check that attempted to prove that the
impl<T> Drop
was parametric with respect toT
. This would mean that there would be more warning cycle;dropck
would simply start rejecting more code. There would be no way to opt back into the olddropck
rules. -
(However, during implementation
実装of this change, we should double-check whether a warning-cycle is in fact feasible.)
unguarded-escape-hatch
The heart of unguarded-escape-hatch
(UGEH) is this: Provide a new, unsafe (and unstable) attribute-based escape hatch for use in the standard library for cases where Drop Check is too strict.
Why we need an escape hatch
The original motivation for the parametricity special-case in the original Drop-Check rule was due to an observation that collection types such as TypedArena<T>
or Vec<T>
were often used to contain
An example would be an elementstruct
, and then instantiations of TypedArena<Concrete>
or Vec<Concrete>
. This pattern has been used within rustc
, for example, to store elements
Without the parametricity special-case, the existence of a destructor on TypedArena<T>
or Vec<T>
led the Drop-Check analysisT
-- forcing dropck
to reject those destructors.
(Note that Concrete
itself has no destructor; if it did, then dropck
, both as originally stated and under the changes of this RFC, would force the 'a
parameterTypedArena
or Vec
.)
Of course, the whole point of this RFC is that using parametricity as the escape hatch seems like it does not suffice. But we still need some escape hatch.
The new escape hatch: an unsafe attribute
This leads us to the second componentunguarded-escape-hatch
(UGEH): Add an attribute (with a name starting with "unsafe") that a library designer can attach to a drop
implementationdropck
's constraints
This RFC proposes the attribute name unsafe_destructor_blind_to_params
. This name was specifically
Much like the unsafe_destructor
attribute that we had in the past, this attribute relies on the programmer to ensuredrop
(and all functions that this drop
may transitively call) will never read or modify a value of any type parameter,
- (In particular, it certainly must not dereference any
&
-reference within such a value, though this RFC is adopts a somewhat stronger requirement to encourage the attribute to only be used for the limited case of parametric collection types, where one need not do anything more than move or drop values.)
The above assumption must hold
UGEH stabilization details
The proposed attribute is only a short-term patch to work-around a bug exposed by the combination of two desirable features (namely impl specialization and dropck
).
In particular, using the attribute in cases where control-flow in the destructor can reach functions that may be specialized on a type-parameter T
may expose the system to use-after-free scenarios or other unsound conditions.
-
Short term
項、用語strategy: The working assumption of this RFC is that the standard library developers will use the proposed attribute in cases where the destructor is parametric with respect to all type parameters,仮引数even though the compiler cannot currently prove this to be the case.The new attribute will be restricted
制限するto non-stable channels, like any other new feature under a feature-gate. -
Long term
項、用語strategy: This RFC does not make any formal guarantees保証するabout the long-term strategy for including an escape hatch. In particular, this RFC does not propose that we stabilize the proposed attributeIt may be possible for future language
言語changes to allow許可する、可能にするus to directly直接express the necessary parametricity properties.特徴、性質See furtherさらなる、それ以上discussion in the continue supporting parametricity alternative.代わりのもの、選択肢The suggested attribute name (
unsafe_destructor_blind_to_params
above) was deliberately selected to be long and ugly, in order順序to discourage it from being stabilized in the future without at least some significant discussion. (Likewise, the acronym "UGEH" was chosen for its likely pronounciation "ugh", again a reminder that we do not want to adopt this approach for the long term.)
Examples of code changes under the RFC
This section
Examples of code that must continue to work
Here is some code that works today and must continue to work in the future:
use std::cell::Cell;
struct Concrete<'a>(u32, Cell<Option<&'a Concrete<'a>>>);
fn main() {
let mut data = Vec::new();
data.push(Concrete(0, Cell::new(None)));
data.push(Concrete(0, Cell::new(None)));
data[0].1.set(Some(&data[1]));
data[1].1.set(Some(&data[0]));
}
In the above, we are building up a vector, pushing Concrete
elements
We can even wrap the vector in a struct
use std::cell::Cell;
struct Concrete<'a>(u32, Cell<Option<&'a Concrete<'a>>>);
struct Foo<T> { data: Vec<T> }
fn main() {
let mut foo = Foo { data: Vec::new() };
foo.data.push(Concrete(0, Cell::new(None)));
foo.data.push(Concrete(0, Cell::new(None)));
foo.data[0].1.set(Some(&foo.data[1]));
foo.data[1].1.set(Some(&foo.data[0]));
}
Examples of code that will start to be rejected
The main change injected by this RFC is this: due to cannot-assume-parametricity
, an attempt to add a destructor to the struct
above will causeFoo
may invoke
Thus,
use std::cell::Cell;
struct Concrete<'a>(u32, Cell<Option<&'a Concrete<'a>>>);
struct Foo<T> { data: Vec<T> }
// This is the new `impl Drop`
impl<T> Drop for Foo<T> {
fn drop(&mut self) { }
}
fn main() {
let mut foo = Foo { data: Vec::new() };
foo.data.push(Concrete(0, Cell::new(None)));
foo.data.push(Concrete(0, Cell::new(None)));
foo.data[0].1.set(Some(&foo.data[1]));
foo.data[1].1.set(Some(&foo.data[0]));
}
NOTE: Basedcrates.io
actually regressed under the new rule: everything that compiled before the change continued to compile after it.
Example of the unguarded-escape-hatch
If the developer of Foo
has access to the feature-gated escape-hatch, and is willing to assert that the destructor for Foo
does nothing with the links in the data, then the developer can work around the above rejection of the code by adding
#![feature(dropck_parametricity)]
use std::cell::Cell;
struct Concrete<'a>(u32, Cell<Option<&'a Concrete<'a>>>);
struct Foo<T> { data: Vec<T> }
impl<T> Drop for Foo<T> {
#[unsafe_destructor_blind_to_params] // This is the UGEH attribute
fn drop(&mut self) { }
}
fn main() {
let mut foo = Foo { data: Vec::new() };
foo.data.push(Concrete(0, Cell::new(None)));
foo.data.push(Concrete(0, Cell::new(None)));
foo.data[0].1.set(Some(&foo.data[1]));
foo.data[1].1.set(Some(&foo.data[0]));
}
Drawbacks
As should be clear by the tone of this RFC, the unguarded-escape-hatch
is clearly a hack. It is subtle and unsafe, just as unsafe_destructor
was (and for the most part, the whole point of Sound Generic Drop was to remove unsafe_destructor
from the language).
-
However, the expectation is that most clients will have no need to ever use the
unguarded-escape-hatch
. -
It may suffice to use the escape hatch solely
単に/単独でwithin the collection types oflibstd
. -
Otherwise,
さもなければif clients outside oflibstd
determine決定するthat they do need to be able to write destructors that need to bypassdropck
safely, then we can (and should) investigate one of the sound alternatives, rather than stabilize the unsafe hackish escape hatch..
Alternatives
CAP without UGEH
One might considercannot-assume-parametricity
without unguarded-escape-hatch
. However, unless some other sort of escape hatch were added,
UGEH for lifetime parameters仮引数
Since we're already being unsafe here, one might considerunsafe_destructor_blind_to_params
apply
However, givenunsafe_destructor_blind_to_params
attribute is only intended as a short-term band-aid (see UGEH stabilization details) it seems better to just make it only as broad as it needs to be (and no broader).
"Sort-of Guarded" Escape Hatch
We could add the escape hatch but continue employing the current dropck analysis
I only mention this because it occurreddropck
would catch some mistakes in programmer reasoning) but the pitfalls with respect to specialization would remain.
Continue Supporting Parametricity
There may be ways to revise the language
Neither design
(Also, if we go down this path, we will need to fix other bugs in the Drop Check rule, where, as previously noted, parametricity is a necessary but insufficient condition
Parametricity via effect-system attributes
One feature of the impl specialization RFC is that all functions that can be specialized must be declareddefault
keyword.
This leads us to one way that a function could declare#[unspecialized]
. The #[unspecialized]
attribute, when appliedfn foo()
, would mean two things:
-
foo
is not allowed許可する、可能にするto call呼び出しany functions that have thedefault
keyword. -
foo
is only allowed許可する、可能にするto call呼び出しfunctions that are also marked#[unspecialized]
All fn drop
methods would be required to be #[unspecialized]
.
It is the second bullet that makes this an ad-hoc effect system: it providesfoo
, we will never invokedefault
(and therefore, I think, will never even potentially invoke
It is also this second bullet that represents#[unspecialized]
. The attribute is unlikely to be included on any function unless the its developer is making a destructor that calls
Parametricity via some ?
-bound
Another approach starts from another angle: As describeddropck
is the requirement that fn drop
cannot do anything with a t: T
(where T
is some relevant type parameter) except:
-
move
t
to some other owner expecting aT
or, -
drop
t
, running its destructor and freeing associated関連付けられたresources.
So, perhaps it would be more natural to express this requirement via a bound.
We would start with the assumption that functions may be non-parametric (and thus
But then if you want to declareT: ?Special
.
The Drop-check rule would treatT: ?Special
type-parameters as parametric, and other type-parameters as non-parametric.
The marker trait Special
would be an OIBIT that all sized types would get.
Any expression<T: ?Special>
would not be alloweddefault
method where T
could affect the specialization process.
(The careful reader will probably notice the potential sleight-of-hand here: is this really any different from the effect-system attributes proposed earlier? Perhaps not, though it seems likely that the finer grain parameter-specific treatment proposed here is more expressive, at least in theory.)
Like the previousT: ?Special
attribute is unlikely to be included on any function unless the its developer is making a destructor that calls
Unresolved questions
-
What name to use for the attribute? Is
unsafe_destructor_blind_to_params
sufficiently十分にlong and ugly? ;) -
What is the real long-term plan?
-
Should we consider
考える、みなすmerging the discussion of alternatives into the impl specialization RFC?
Bibliography
Reynolds
John C. Reynolds. "Types, abstraction and parametric polymorphism". IFIP 1983 http://www.cse.chalmers.se/edu/year/2010/course/DAT140_Types/Reynolds_typesabpara.pdf
Wadler
Philip Wadler. "Theorems for free!". FPCA 1989 http://ttic.uchicago.edu/~dreyer/course/papers/wadler.pdf