Copy path View file
@@ -0,0 +1,584 @@
- Feature Name: dropck_eyepatch, generic_param_attrs
- Start Date: 2015-10-19
- RFC PR: [rust-lang/rfcs#1327](https://github.com/rust-lang/rfcs/pull/1327)
- Rust Issue: [rust-lang/rust#34761](https://github.com/rust-lang/rust/issues/34761)

# Summary
[summary]: #summary

Refine the unguarded-escape-hatch from [RFC 1238][] (nonparametric
dropck) so that instead of a single attribute side-stepping *all*
dropck constraints for a type's destructor, we instead have a more
focused system that specifies exactly which type and/or lifetime
parameters the destructor is guaranteed not to access.

Specifically, this RFC proposes adding the capability to attach
attributes to the binding sites for generic parameters (i.e. lifetime
and type paramters). Atop that capability, this RFC proposes adding a
`#[may_dangle]` attribute that indicates that a given lifetime or type
holds data that must not be accessed during the dynamic extent of that
`drop` invocation.

As a side-effect, enable adding attributes to the formal declarations
of generic type and lifetime parameters.

[RFC 1238]: https://github.com/rust-lang/rfcs/blob/master/text/1238-nonparametric-dropck.md
[RFC 769]: https://github.com/rust-lang/rfcs/blob/master/text/0769-sound-generic-drop.md

# Motivation
[motivation]: #motivation

The unguarded escape hatch (UGEH) from [RFC 1238] is a blunt
instrument: when you use `unsafe_destructor_blind_to_params`, it is
asserting that your destructor does not access borrowed data whose
type includes *any* lifetime or type parameter of the type.

For example, the current destructor for `RawVec<T>` (in `liballoc/`)
looks like this:

```rust
impl<T> Drop for RawVec<T> {
#[unsafe_destructor_blind_to_params]
/// Frees the memory owned by the RawVec *without* trying to Drop its contents.
fn drop(&mut self) {
[... free memory using global system allocator ...]
}
}
```

The above is sound today, because the above destructor does not call
any methods that can access borrowed data in the values of type `T`,
and so we do not need to enforce the drop-ordering constraints imposed
when you leave out the `unsafe_destructor_blind_to_params` attribute.

While the above attribute suffices for many use cases today, it is not
fine-grain enough for other cases of interest. In particular, it
cannot express that the destructor will not access borrowed data
behind a *subset* of the type parameters.

Here are two concrete examples of where the need for this arises:

## Example: `CheckedHashMap`

The original Sound Generic Drop proposal ([RFC 769][])
had an [appendix][RFC 769 CheckedHashMap] with an example of a
`CheckedHashMap<K, V>` type that called the hashcode method
for all of the keys in the map in its destructor.
This is clearly a type where we *cannot* claim that we do not access
borrowed data potentially hidden behind `K`, so it would be unsound
to use the blunt `unsafe_destructor_blind_to_params` attribute on this
type.

However, the values of the `V` parameter to `CheckedHashMap` are, in
all likelihood, *not* accessed by the `CheckedHashMap` destructor. If
that is the case, then it should be sound to instantiate `V` with a
type that contains references to other parts of the map (e.g.,
references to the keys or to other values in the map). However, we
cannot express this today: There is no way to say that the
`CheckedHashMap` will not access borrowed data that is behind *just*
`V`.

[RFC 769 CheckedHashMap]: https://github.com/rust-lang/rfcs/blob/master/text/0769-sound-generic-drop.md#appendix-a-why-and-when-would-drop-read-from-borrowed-data

## Example: `Vec<T, A:Allocator=DefaultAllocator>`

The Rust developers have been talking for [a long time][RFC Issue 538]
about adding an `Allocator` trait that would allow users to override
the allocator used for the backing storage of collection types like
`Vec` and `HashMap`.

For example, we would like to generalize the `RawVec` given above as
follows:

```rust
#[unsafe_no_drop_flag]
pub struct RawVec<T, A:Allocator=DefaultAllocator> {
ptr: Unique<T>,
cap: usize,
alloc: A,
}
impl<T, A:Allocator> Drop for RawVec<T, A> {
#[should_we_put_ugeh_attribute_here_or_not(???)]
/// Frees the memory owned by the RawVec *without* trying to Drop its contents.
fn drop(&mut self) {
[... free memory using self.alloc ...]
}
}
```

However, we *cannot* soundly add an allocator parameter to a
collection that today uses the `unsafe_destructor_blind_to_params`
UGEH attribute in the destructor that deallocates, because that blunt
instrument would allow someone to write this:

```rust
// (`ArenaAllocator`, when dropped, automatically frees its allocated blocks)
// (Usual pattern for assigning same extent to `v` and `a`.)
let (v, a): (Vec<Stuff, &ArenaAllocator>, ArenaAllocator);
a = ArenaAllocator::new();
v = Vec::with_allocator(&a);
... v.push(stuff) ...
// at end of scope, `a` may be dropped before `v`, invalidating
// soundness of subsequent invocation of destructor for `v` (because
// that would try to free buffer of `v` via `v.buf.alloc` (== `&a`)).
```

The only way today to disallow the above unsound code would be to
remove `unsafe_destructor_blind_to_params` from `RawVec`/ `Vec`, which
would break other code (for example, code using `Vec` as the backing
storage for [cyclic graph structures][dropck_legal_cycles.rs]).

[RFC Issue 538]: https://github.com/rust-lang/rfcs/issues/538

[dropck_legal_cycles.rs]: https://github.com/rust-lang/rust/blob/098a7a07ee6d11cf6d2b9d18918f26be95ee2f66/src/test/run-pass/dropck_legal_cycles.rs

# Detailed design
[detailed design]: #detailed-design

1. Add the ability to attach attributes to syntax that binds formal
lifetime or type parmeters. For the purposes of this RFC, the only
place in the syntax that requires such attributes are `impl`
blocks, as in `impl<T> Drop for Type<T> { ... }`

2. Add a new fine-grained attribute, `may_dangle`, which is attached
to the binding sites for lifetime or type parameters on an `Drop`
implementation.
This RFC will sometimes call this attribute the "eyepatch",
since it does
not make dropck totally blind; just blind on one "side".

3. Add a new requirement that any `Drop` implementation that uses the
`#[may_dangle]` attribute must be declared as an `unsafe impl`.
This reflects the fact that such `Drop` implementations have
an additional constraint on their behavior (namely that they cannot
access certain kinds of data) that will not be verified by the
compiler and thus must be verified by the programmer.

4. Remove `unsafe_destructor_blind_to_params`, since all uses of it
should be expressible via `#[may_dangle]`.

## Attributes on lifetime or type parameters

This is a simple extension to the syntax.

It is guarded by the feature gate `generic_param_attrs`.

Constructions like the following will now become legal.

Example of eyepatch attribute on a single type parameter:
```rust
unsafe impl<'a, #[may_dangle] X, Y> Drop for Foo<'a, X, Y> {
...
}
```

Example of eyepatch attribute on a lifetime parameter:
```rust
unsafe impl<#[may_dangle] 'a, X, Y> Drop for Bar<'a, X, Y> {
...
}
```

Example of eyepatch attribute on multiple parameters:
```rust
unsafe impl<#[may_dangle] 'a, X, #[may_dangle] Y> Drop for Baz<'a, X, Y> {
...
}
```

These attributes are only written next to the formal binding
sites for the generic parameters. The *usage* sites, points
which refer back to the parameters, continue to disallow the use
of attributes.

So while this is legal syntax:

```rust
unsafe impl<'a, #[may_dangle] X, Y> Drop for Foo<'a, X, Y> {
...
}
```

the follow would be illegal syntax (at least for now):

```rust
unsafe impl<'a, X, Y> Drop for Foo<'a, #[may_dangle] X, Y> {
...
}
```


## The "eyepatch" attribute

Add a new attribute, `#[may_dangle]` (the "eyepatch").

It is guarded by the feature gate `dropck_eyepatch`.

The eyepatch is similar to `unsafe_destructor_blind_to_params`: it is
part of the `Drop` implementation, and it is meant
to assert that a destructor is guaranteed not to access certain kinds
of data accessible via `self`.

The main difference is that the eyepatch is applied to a single
generic parameter: `#[may_dangle] ARG`.
This specifies exactly *what*
the destructor is blind to (i.e., what will dropck treat as
inaccessible from the destructor for this type).

There are two things one can supply as the `ARG` for a given eyepatch:
one of the type parameters for the type,
or one of the lifetime parameters
for the type.

When used on a type, e.g. `#[may_dangle] T`, the programmer is
asserting the only uses of values of that type will be to move or drop
them. Thus, no fields will be accessed nor methods called on values of
such a type (apart from any access performed by the destructor for the
type when the values are dropped). This ensures that no dangling
references (such as when `T` is instantiated with `&'a u32`) are ever
accessed in the scenario where `'a` has the same lifetime as the value
being currently destroyed (and thus the precise order of destruction
between the two is unknown to the compiler).

When used on a lifetime, e.g. `#[may_dangle] 'a`, the programmer is
asserting that no data behind a reference of lifetime `'a` will be
accessed by the destructor. Thus, no fields will be accessed nor
methods called on values of type `&'a Struct`, ensuring that again no
dangling references are ever accessed by the destructor.

## Require `unsafe` on Drop implementations using the eyepatch

The final detail is to add an additional check to the compiler
to ensure that any use of `#[may_dangle]` on a `Drop` implementation
imposes a requirement that that implementation block use
`unsafe impl`.<sup>[2](#footnote1)</sup>

This reflects the fact that use of `#[may_dangle]` is a
programmer-provided assertion about the behavior of the `Drop`
implementation that must be valided manually by the programmer.
It is analogous to other uses of `unsafe impl` (apart from the
fact that the `Drop` trait itself is not an `unsafe trait`).

### Examples adapted from the Rustonomicon

[nomicon dropck]: https://doc.rust-lang.org/nightly/nomicon/dropck.html

So, adapting some examples from the Rustonomicon
[Drop Check][nomicon dropck] chapter, we would be able to write
the following.

Example of eyepatch on a lifetime parameter::

```rust
struct InspectorA<'a>(&'a u8, &'static str);
unsafe impl<#[may_dangle] 'a> Drop for InspectorA<'a> {
fn drop(&mut self) {
println!("InspectorA(_, {}) knows when *not* to inspect.", self.1);
}
}
```

Example of eyepatch on a type parameter:

```rust
use std::fmt;
struct InspectorB<T: fmt::Display>(T, &'static str);
unsafe impl<#[may_dangle] T: fmt::Display> Drop for InspectorB<T> {
fn drop(&mut self) {
println!("InspectorB(_, {}) knows when *not* to inspect.", self.1);
}
}
```

Both of the above two examples are much the same as if we had used the
old `unsafe_destructor_blind_to_params` UGEH attribute.

### Example: RawVec

To generalize `RawVec` from the [motivation](#motivation) with an
`Allocator` correctly (that is, soundly and without breaking existing
code), we would now write:

```rust
unsafe impl<#[may_dangle]T, A:Allocator> Drop for RawVec<T, A> {
/// Frees the memory owned by the RawVec *without* trying to Drop its contents.
fn drop(&mut self) {
[... free memory using self.alloc ...]
}
}
```

The use of `#[may_dangle] T` here asserts that even
though the destructor may access borrowed data through `A` (and thus
dropck must impose drop-ordering constraints for lifetimes occurring
in the type of `A`), the developer is guaranteeing that no access to
borrowed data will occur via the type `T`.

The latter is not expressible today even with
`unsafe_destructor_blind_to_params`; there is no way to say that a
type will not access `T` in its destructor while also ensuring the
proper drop-ordering relationship between `RawVec<T, A>` and `A`.

### Example; Multiple Lifetimes

Example: The above `InspectorA` carried a `&'static str` that was
always safe to access from the destructor.

If we wanted to generalize this type a bit, we might write:

```rust
struct InspectorC<'a,'b,'c>(&'a str, &'b str, &'c str);
unsafe impl<#[may_dangle] 'a, 'b, #[may_dangle] 'c> Drop for InspectorC<'a,'b,'c> {
fn drop(&mut self) {
println!("InspectorA(_, {}, _) knows when *not* to inspect.", self.1);
}
}
```

This type, like `InspectorA`, is careful to only access the `&str`
that it holds in its destructor; but now the borrowed string slice
does not have `'static` lifetime, so we must make sure that we do not
claim that we are blind to its lifetime (`'b`).

(This example also illustrates that one can attach multiple instances
of the eyepatch attribute to a destructor, each with a distinct input
for its `ARG`.)

Given the definition above, this code will compile and run properly:

```rust
fn this_will_work() {
let b; // ensure that `b` strictly outlives `i`.
let (i,a,c);
a = format!("a");
b = format!("b");
c = format!("c");
i = InspectorC(a, b, c);
}
```

while this code will be rejected by the compiler:

```rust
fn this_will_not_work() {
let (a,c);
let (i,b); // OOPS: `b` not guaranteed to survive for `i`'s destructor.
a = format!("a");
b = format!("b");
c = format!("c");
i = InspectorC(a, b, c);
}
```

## Semantics

How does this work, you might ask?

The idea is actually simple: the dropck rule stays mostly the same,
except for a small twist.

The Drop-Check rule at this point essentially says:

> if the type of `v` owns data of type `D`, where
>
> (1.) the `impl Drop for D` is either type-parametric, or lifetime-parametric over `'a`, and
> (2.) the structure of `D` can reach a reference of type `&'a _`,
>
> then `'a` must strictly outlive the scope of `v`
The main change we want to make is to the second condition.
Instead of just saying "the structure of `D` can reach a reference of type `&'a _`",
we want first to replace eyepatched lifetimes and types within `D` with `'static` and `()`,
respectively. Call this revised type `patched(D)`.

Then the new condition is:

> (2.) the structure of patched(D) can reach a reference of type `&'a _`,
*Everything* else is the same.

In particular, the patching substitution is *only* applied with
respect to a particular destructor. Just because `Vec<T>` is blind to `T`
does not mean that we will ignore the actual type instantiated at `T`
in terms of drop-ordering constraints.

For example, in `Vec<InspectorC<'a,'name,'c>>`, even though `Vec`
itself is blind to the whole type `InspectorC<'a, 'name, 'c>` when we
are considering the `impl Drop for Vec`, we *still* honor the
constraint that `'name` must strictly outlive the `Vec` (because we
continue to consider all `D` that is data owned by a value `v`,
including when `D` == `InspectorC<'a,'name,'c>`).

## Prototype
[prototype]: #prototype

pnkfelix has implemented a proof-of-concept
[implementation][pnkfelix prototype] of the `#[may_dangle]` attribute.
It uses the substitution machinery we already have in the compiler
to express the semantics above.

## Limitations of prototype (not part of design)

Here we note a few limitations of the current prototype. These
limitations are *not* being proposed as part of the specification of
the feature.

<a name="footnote1">2.</a> The compiler does not yet enforce (or even
allow) the use of `unsafe impl` for `Drop` implementations that use
the `#[may_dangle]` attribute.

Fixing the above limitations should just be a matter of engineering,
not a fundamental hurdle to overcome in the feature's design in the
context of the language.

[pnkfelix prototype]: https://github.com/pnkfelix/rust/commits/dropck-eyepatch

# Drawbacks
[drawbacks]: #drawbacks

## Ugliness

This attribute, like the original `unsafe_destructor_blind_to_params`
UGEH attribute, is ugly.

## Unchecked assertions boo

It would be nicer if to actually change the language in a way where we
could check the assertions being made by the programmer, rather than
trusting them. (pnkfelix has some thoughts on this, which are mostly
reflected in what he wrote in the [RFC 1238 alternatives][].)

[RFC 1238 alternatives]: https://github.com/rust-lang/rfcs/blob/master/text/1238-nonparametric-dropck.md#continue-supporting-parametricity

# Alternatives
[alternatives]: #alternatives

## Make dropck "see again" via (focused) where-clauses

The idea is that we keep the UGEH attribute, blunt hammer that it is.
You first opt out of the dropck ordering constraints via that, and
then you add back in ordering constraints via `where` clauses.

(The ordering constraints in question would normally be *implied* by
the dropck analysis; the point is that UGEH is opting out of that
analysis, and so we are now adding them back in.)

Here is the allocator example expressed in this fashion:

```rust
impl<T, A:Allocator> Drop for RawVec<T, A> {
#[unsafe_destructor_blind_to_params]
/// Frees the memory owned by the RawVec *without* trying to Drop its contents.
fn drop<'s>(&'s mut self) where A: 's {
// ~~~~~~~~~~~
// |
// |
// This constraint (that `A` outlives `'s`), and other conditions
// relating `'s` and `Self` are normally implied by Rust's type
// system, but `unsafe_destructor_blind_to_params` opts out of
// enforcing them. This `where`-clause is opting back into *just*
// the `A:'s` again.
//
// Note we are *still* opting out of `T: 's` via
// `unsafe_destructor_blind_to_params`, and thus our overall
// goal (of not breaking code that relies on `T` not having to
// survive the destructor call) is accomplished.
[... free memory using self.alloc ...]
}
}
```

This approach, if we can make it work, seems fine to me. It certainly
avoids a number of problems that the eyepatch attribute has.

Advantages of fn-drop-with-where-clauses:

* Since the eyepatch attribute is to be limited to type and lifetime
parameters, this approach is more expressive,
since it would allow one to put type-projections into the
constraints.

Drawbacks of fn-drop-with-where-clauses:

* Its not 100% clear what our implementation strategy will be for it,
while the eyepatch attribute does have a [prototype].

I actually do not give this drawback much weight; resolving this
may be merely a matter of just trying to do it: e.g., build up the
set of where-clauses when we make the ADT's representatin, and
then have `dropck` insert instantiate and insert them as needed.

* It might have the wrong ergonomics for developers: It seems bad to
have the blunt hammer introduce all sorts of potential
unsoundness, and rely on the developer to keep the set of
`where`-clauses on the `fn drop` up to date.

This would be a pretty bad drawback, *if* the language and
compiler were to stagnate. But my intention/goal is to eventually
put in a [sound compiler analysis][wait-for-proper-parametricity].
In other words, in the future, I will be more concerned about the
ergonomics of the code that uses the sound analysis. I will not be
concerned about "gotcha's" associated with the UGEH escape hatch.

(The most important thing I want to convey is that I believe that both
the eyepatch attribute and fn-drop-with-where-clauses are capable of
resolving the real issues that I face today, and I would be happy for
either proposal to be accepted.)

## Wait for proper parametricity
[wait-for-proper-parametricity]: #wait-for-proper-parametricity

As alluded to in the [drawbacks][], in principle we could provide
similar expressiveness to that offered by the eyepatch (which is
acting as a fine-grained escape hatch from dropck) by instead offering
some language extension where the compiler would actually analyze the
code based on programmer annotations indicating which types and
lifetimes are not used by a function.

In my opinion I am of two minds on this (but they are both in favor
this RFC rather than waiting for a sound compiler analysis):

1. We will always need an escape hatch. The programmer will always need
a way to assert something that she knows to be true, even if the compiler
cannot prove it. (A simple example: Calling a third-party API that has not
yet added the necessary annotations.)

This RFC is proposing that we keep an escape hatch, but we make it more
expressive.

2. If we eventually *do* have a sound compiler analysis, I see the
compiler changes and library annotations suggested by this RFC as
being in line with what that compiler analysis would end up using
anyway. In other words: Assume we *did* add some way for the programmer
to write that `T` is parametric (e.g. `T: ?Special` in the [RFC 1238 alternatives]).
Even then, we would still need the compiler changes suggested by this RFC,
and at that point hopefully the task would be for the programmer to mechanically
replace occurrences of `#[may_dangle] T` with `T: ?Special`
(and then see if the library builds).

In other words, I see the form suggested by this RFC as being a step *towards*
a proper analysis, in the sense that it is getting programmers used to thinking
about the individual parameters and their relationship with the container, rather
than just reasoning about the container on its own without any consideration
of each type/lifetime parameter.

## Do nothing

If we do nothing, then we cannot add `Vec<T, A:Allocator>` soundly.

# Unresolved questions
[unresolved]: #unresolved-questions

Is the definition of the drop-check rule sound with this `patched(D)`
variant? (We have not proven any previous variation of the rule
sound; I think it would be an interesting student project though.)