Skip to content
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

Dropck Eyepatch RFC. #1327

Merged
merged 9 commits into from
Jul 11, 2016
Merged

Dropck Eyepatch RFC. #1327

merged 9 commits into from
Jul 11, 2016

Conversation

pnkfelix
Copy link
Member

@pnkfelix pnkfelix commented Oct 19, 2015

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 attribute that specifies exactly which type and/or lifetime parameters the destructor is guaranteed not to access.

  • For Prototype Impl: still need to implement unsafe impl requirement
  • For RFC: the new kind of attribute (on generic parameter binding sites) should have its own, separate, feature gate.

rendered

@pnkfelix pnkfelix changed the title first draft of dropck-eyepatch RFC. Dropck Eyepatch RFC. Oct 19, 2015
@pnkfelix
Copy link
Member Author

cc @nikomatsakis @arielb1

@arielb1
Copy link
Contributor

arielb1 commented Oct 19, 2015

attributes taking IDENTIFIERS as parameters? how unhygienic are you willing to go? /justjoking. Anyway, it would be nice to put arbitrary types (most importantly, projections) in the attribute (but this requires it to be a blacklist instead of a whitelist):

trait TypeContext {
    type Arena: Copy + Allocator;
    type Ty: Copy + TypeFoldable<Self>;
    type SubstRef: Copy + TypeFoldable<Self>;
    fn subst<T: TypeFoldable<Self>>(t: T, s: self::SubstRef) -> T;
}

struct TyList<TCX: TypeContext> {
    d: Unique<[TCX::Ty]>,
    a: TCX::Arena
}

impl<TCX: TypeContext> Drop for DataSlice<C> {
    #[unsafe_destructor_accesses_only(TCX::Arena)]
    fn drop(&mut self) {
        unsafe { self.a.deallocate(d) };
    }
}

@arielb1
Copy link
Contributor

arielb1 commented Oct 19, 2015

I didn't really read the RFC throughly. I would prefer to add T: 'a predicates directly, like what is done with dtorck types today for Self, rather than doing "structural-outlives" for Self with some if its contents changed (the semantics of outlives means this ends up basically identical, except for types only present via an associated type).

@arielb1
Copy link
Contributor

arielb1 commented Oct 19, 2015

Actually, the "structural-outlives" is just me misinterpreting the RFC. It still could be clearer about that.

@arielb1
Copy link
Contributor

arielb1 commented Oct 19, 2015

It may be cleaner (but less ergonomic) to just allow putting where-clauses (of course, clauses implied by Drop::drop's signature) on the drop method and having them be obeyed.

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 {
        [... free memory using self.alloc ...]
    }
}

OTOH, this won't really work because of early vs. late-bound lifetimes.

@pnkfelix
Copy link
Member Author

attributes taking IDENTIFIERS as parameters? how unhygienic are you willing to go? /justjoking.

This is actually a fairly significant drawback to the approach.

@arielb1
Copy link
Contributor

arielb1 commented Oct 19, 2015

Except for the hygiene and projection problems, the "negative-sense" approach is nicer ergonomically.

However, I think the "positive-sense" approach is much cleaner from a semantic standpoint. It meshes nicely with RFC1214.

A model for dropck

Terminology: outlives and alive

T: 'a or equivalently "T outlives 'a" is the relation from RFC1214. T alive means that T outlives the current scope.

Problem statement

We are mainly interested in variants of the following example:

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]));
}

main, when translated to (cleaned) MIR, is

00  $data : Vec<Concrete<'λ>> = call Vec::new() uwto panic0
01  call Vec::push(&'01 mut $data, Concrete(0, Cell(None))) uwto panic1
02  call Vec::push(&'02 mut $data, Concrete(0, Cell(None))) uwto panic1
    ; bound checks omitted and deref CSE-d because of irrelevance
03  $α : &'μ Concrete<'λ> = call Vec::deref(&'μ data)
04  call Cell::set(&'04 $α[0].1, Some(&'λ $α[1])) uwto panic2
05  call Cell::set(&'05 $α[1].1, Some(&'λ $α[0])) uwto panic2
06  $retval : () = () ; just be pedantic
07  drop $α uwto panic1
08  drop $data uwto panic0
09  return
panic2:
10  drop $α uwto ⊥
panic1:
11  drop $data uwto ⊥
panic0:
12  unwind

We have to regionck this code, which means assigning concrete regions to 'λ and 'μ. In this example, we can basically let them be equal without losing generality.

Naïvely, we might think we could replace drop X with call std::mem::drop(X) (assuming we are using filling drop). However, in that case, the call would force Vec<Concrete<'λ>>: '08, which will make 'λ contain at least 00-08, which would lead to a borrowck conflict (a "borrow does not live long enough" with the current scope-based system) between the drop and the borrow. As we want this code to compile, we must drop this restriction - allow doing an operation on types that are not alive.

Another way of seeing this is that because the references are cyclic, one of the destructors will be called after the destructor was called on the other.

dropck and the escape hatch

The observation that allows dropck to work is that many destructors don't really care about the value they hold. For example, the drop glue for references, being completely trivial, runs just as well if the reference is dead. We basically want a T destruction-valid-for 'α relation, that weaker than T: 'α but still good enough. As the destructor of Vec<Concrete<'λ>> does not actually care about being alive, we can let be 00-05 and have a program that compiles and runs.

Obviously, the drop glue for types with a arbitrary user-defined destructor calls <T as Drop>::drop, which is allowed to assume T is alive, so for these T destruction-valid-for 'α is exactly T: 'a.

On the other hand, the drop glue for types without destructors just calls the drop-glue of their contents. We can therefore extend destruction-valid-for over these by trivial structural recursion (note that as long as the type is well-formed this is implied by it being alive).

The first interesting case is types, like Vec, that have a user-defined destructor that behaves essentially like drop glue, being sound to run as long as its content types are destruction-valid without requiring them to be alive. In this case, the destructor function is called, and can call other functions, with a type parameter that isn't actually alive. This is just as dangerous as it sounds, but destructors are typically unsafe for other reasons anyway. We used to have a scheme relying on parametricity to automatically find these types, but it proved to be more trouble than it's worth, and now we require them to be annotated with #[unsafe_destructor_blind_to_params].

The second case is what this RFC is about, types that require their content to be destruction-valid, but in addition require some type parameter, for example an allocator, to be alive. These are a combination of the previous cases, and behave similarly.

dealing with dead types

While dead type parameters are certainly dangerous, it is still possible to write sound generic programs that deal with them. I will discuss this tomorrow.

@arielb1
Copy link
Contributor

arielb1 commented Oct 20, 2015

dealing with dead types

The primary soundness issue raised by dropck is that functions (the destructor itself, and potentially generic functions called by it) are called with types and lifetimes that are not actually alive. Now lifetimes that are not alive are very dangerous - a reference with that lifetime points to dead data, and accessing it will cause UB and bugs that are hard to debug and easy to exploit.

Types that are not alive, however, are only dangerous because of the dead lifetimes they can contain. As the only way a lifetime can be extracted from a type in Rust is through a function call (even associated types can't do it!), we only need to ensure these are safe. For this, we differentiate between functions that are still accessible from when the type was alive and newly-created functions.

The function pointers that were accessible from when type was alive are certainly a problem, as was discovered in rust-lang/rust#26656. Because of the way type parameters work, they are only dangerous if their signature contains dead types (informally, dead generic parameters can be "truncated" back to life - but this is still a problem without dropck, though it would be nice if someone formalized it), so you must ensure no old function with such signature is called. A similar problem would exist with trait objects, except that the current formulation of destruction-validity forces them to be alive.

The other problematic case is methods from trait-refs implied by where-clauses in the destructor (in fact, trait objects and function pointers can conceptually be treated as though they had implicit Trait<T>: Trait<T> and fn(T): Fn(T) where-clauses this is not entirely accurate, as you can create "new" fn(T) values). We must ensure these are not called - in most cases, none of the bounds have methods, so we are safe (the Drop "implicit bound" does have a method, but destruction-validity keeps that safe).

Free functions and methods that come from trait-refs selected by impls can be proven safe inductively (keeping in mind the "implicit where clause" interpretation of function pointers), so if the destructor can't access dead function pointers or method-bearing trait-refs, it is safe. Of course, specialization would ruin that by basically allowing every function to split apart a type and touch the dead lifetimes inside, and that would need to be handled somehow.

@Ericson2314
Copy link
Contributor

Currently all lifetime and type parameters are alive, right? In the last RFC I proposed doing analogous to Sized and allowing dead quantification via 'a: ?Alive. The idea is that one has no capabilities on a maybe dead lifetime or reference.

impl<T: ?Alive, A: Allocator> Drop for RawVec<T, A> {
    fn drop(&mut self) {
        [... free memory using self.alloc ...]
    }
}

Unless I am missing something, this gives us nice semantics, no hygiene problem, and the better ergonomics of a dead list. Win!

@alexcrichton
Copy link
Member

Tagging as T-lang and assigning to pnkfelix (but feel free to correct!)

@alexcrichton alexcrichton added the T-lang Relevant to the language team, which will review and decide on the RFC. label Oct 21, 2015
@eefriedman
Copy link
Contributor

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.)

We already have transmute() as an escape hatch in cases where the lifetime is visible: any value whose type contains a lifetime 'a can be transmuted to an equivalent value with a lifetime 'static (or any other appropriate lifetime). In cases where the lifetime isn't visible, we can add some sort of type operator which erases lifetimes (e.g. UnsafeEraseLifetimes<T>::T, where UnsafeEraseLifetimes is implemented with some compiler magic for all T).

@pnkfelix
Copy link
Member Author

pnkfelix commented Nov 5, 2015

So, I find aspects of the alternatives proposed in these comments appealing.

In particular, in the long term I would like us to support something like @Ericson2314 's ?Alive bound.

There is still the question of whether we wait for such a thing to be implemented, or if we accept something dirtier like unsafe_blind_to(T) in the short-term, with the understanding that the dirtier thing is not intended to be stabilized -- it is just meant to unblock work in other areas (in this case, the Allocator trait API and clients in libstd).

(If I was sure that I could implement @arielb1 's fn drop<'s>(&'s mut self) where A: 's then I might even argue for that to be the "dirty" option. I have not yet explored putting that in.)

@Ericson2314
Copy link
Contributor

Glad to here you like it! If we go the temporarily-dirty route, it might be convenient to still use my syntax to avoid the hygiene issues.

@pnkfelix
Copy link
Member Author

pnkfelix commented Nov 6, 2015

@Ericson2314 I would only want to adopt your syntax when we actually enforce the constraint it implies, these other short term approaches all have the word "unsafe" in an attribute name because they are not doing the analysis that I believe ?Alive implies

@Ericson2314
Copy link
Contributor

Well, can use a different identifier,<'a: ?UnsafeDestructorBlindTo>. But it's an unimportant suggestion.

@nikomatsakis
Copy link
Contributor

So it's taken me a while to read the comments on this RFC. My feeling is that, like @pnkfelix, I like the T: ?Alive formulation -- and in fact Felix and I had discussed something similar at some point. Our goal there was that we should be able to go back to a "safe" dtor analysis, unlike the current attribute. By safe we mean that the compiler enforces that, if T: ?Alive, then you do not ever potentially access data in T (or at least dereference region pointers).

However, when we tried to dig into just what limitations we should impose, it seemed harder. Part of this was that we were attempting to permit helper fns that could also use fn foo<T:?Alive> to allow themselves to be invoked from within the dtor (or from without). I haven't thought about this in a while, perhaps there is a relatively simple formulation that is sufficiently expressive. (Have to keep virtual fns and methods in mind, too, of course!)

This is not to say I don't like the T: ?Alive idea -- I still think it's the most promising long-term route. But it may be worth adopting something simpler -- and unsafe -- in the short term, much as this RFC proposes. Like @pnkfelix, I feel like if we just adopted the T: ?Alive syntax but gave it unsafe semantics, that would be unsatisfying. If nothing else, it ought to have the word "unsafe" in there somewhere, to indicate to the user that they are responsible for upholding some obligations. Using an attribute feels more hacky and obscure, which is probably a good thing, in this case, since you are (unfortunately) engaging in dark voodoo of a sort.

With respect to @arielb1's suggestion, I feel less good. Partly this is because that suggestion seems to dial back to maximum unsafety (using the attribute) and then let you layer safety back on, versus the original proposal, which degrades from safe down to more unsafe. I guess that if we were going to make this a safe check, I might feel differently -- that is, perhaps we could fashion something where you add #[no_default_param_bounds] to avoid adding the default T: 'body (and maybe the 'a: 'body as well) annotations, and then you can kind of add them back in as you go, and this is all actually safely checked. It might even fall out of dropck, I'd have to think about it. In that case, I would feel better about the proposal. But it'd take some work to design.

Certainly the problem of hygiene is real, though i'm not sure how real. The hygiene algorithm does tag all identifiers (afaik) with colors and so on, so it could be that we could make the "names in attributes" things work. In any case, longer term, if we did adopt an attribute-based solution, I feel like it'd be nicer to annotate the type parameter declaration impl<#[unsafe_blind] T> or something.

EDIT: Minor tweaks.

@pnkfelix
Copy link
Member Author

So I looked into adding T: ?Alive, just to get a feel for how hard it might be. Unfortunately, in its "safe" formulation (i.e. where it expresses a predicate that we would actually expect the compiler to check), it is not clear whether it solves the problem at hand.

(The rest of this comment is basically an elaboration of that last sentence. In other words, I'm spelling out an example of a potential misgiving that @nikomatsakis alluded to above.)


In the discussion here, I'm going to make some assumptions about what <T: ?Alive> means ... but for the most part, my intention is to reuse the existing type-parameter machinery we already have...

In the long term, the reason I see for machinery like <T: ?Alive> is to allow sound definition of helper functions, callable from the destructor, that have knowledge about which parts of the input are inaccessible (i.e. rustc will check that the bodies of such functions will not reference the inaccessible parts, neither directly nor indirectly).

Consider a case like this:

struct Pair<T>(T, fn(&T) -> String);

#[cfg(sound_helper)]
impl<T: ?Alive> Pair<T> {
    fn call_it(&self) {
        println!("Pair says Hi");
    }
}

impl<T: ?Alive> Drop for Pair<T> {
    fn drop(&mut self) {
        self.call_it();
    }
}

The above is an example of sound code. We would like to be able to structure code into helpers like this and to have the compiler check that the helpers are sound.

Now consider:

#[cfg(unsound_helper)]
impl<T: ?Alive> Pair<T> {
    fn call_it(&self) {
        println!("Pair says {}", self.1(&self.0));
    }
}

My objective is to have a system that would reject the above program: the callback held in self.1 does not state in its signature (fn(&T) -> String) that it will not inspect the value of type T. So whatever way we state "data of type T may not be valid/well-formed", it needs to reject an attempt to pass the data to a callback.

The question: Is <T: ?Alive> a reasonable way of stating "data of type T may not be valid/well-formed" ?

I had thought the answer was "yes, it is reasonable." But that was when I thought we would somewhat magically get the checks we desired out of our existing machinery ... In particular, I had hoped that just the presence of the bound T: ?Alive would stop us from calling helpers that do not have a similar bound defined on them. But that bound will not stop us from calling the above callback held in self.1.


footnote: Perhaps I am being too naive or narrow-minded; i.e. maybe extending the type-checker to ensure that if T: ?Alive, then expressions that can safely reach type T could be considered ill-formed. I.e. we could reject expressions of type T, &T, &mut T, &&mut &T, etc, but continue to accept expressions of type *mut T and *const T, as long as the latter do not contain any sub-expressions of the former kind. I will look into that.

  • Update: a potential problem with the footnoted "solution" is that disallowing all expressions of type T may be too restrictive; in particular, one usually expects to be able to do ptr::read in order to drop the dereferenced T; and certainly Vec needs to continue to support dropping of the T's it holds. (But then again, maybe we just say "No, use drop_in_place for that; ptr::read is not for use with T: ?Alive.)

@pnkfelix
Copy link
Member Author

On a totally separate note: this morning I thought of a hack to address the hygiene issue: in addition to allowing the attribute to refer to the types by name, also allow the attribute to reference a type by its index in the generic parameter list.

E.g.: the InspectorB would also be allowed to be written like this:

impl<T: fmt::Display> Drop for InspectorB<T> {
    #[unsafe_destructor_blind_to(0)]
    fn drop(&mut self) {
        println!("InspectorB(_, {}) knows when *not* to inspect.", self.1);
    }
}

(Ugly, you say? Well, yes: But the above syntax is not required outside of a macro body, so its use should be very rare.)


But what about lifetimes, I hear you cry? Yeah, I didn't think of them either until I reviewed the RFC to find the example above. Well, we could do this (here taking the InspectorC example:

struct InspectorC<'a,'b,'c>(&'a str, &'b str, &'c str);

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

(Boy if you thought the first example was ugly, I bet your eyes are melting now.)

@nikomatsakis
Copy link
Contributor

I think the hygiene issues, while an interesting point, may be overblown. IMO the best way to do this attribute would be to attach it to the type parameter itself impl<#[blind] T> instead of putting it on the drop method, but that would require some other changes that are beyond the scope of this RFC I suspect. So let's just go forward assuming we do a string-comparison, ignoring hygiene (and maybe report an error if there is more than one matching type parameter in scope).

(As an aside, It's worth pointing out that we do not treat type parameter names hygienically now: http://is.gd/XnMSQs, so this is all in some theoretical universe where that is fixed, and yet where we cannot make the macro system aware of identifiers in attributes for some reason.)

Even then, there is no nesting of definitions, so to have a "false equality" you would have to have a macro that is generating the contents of the attribute (i.e., the macro author wrote #[unsafe_destructor_blind_to(X)]) but did not generate the names of the type parameters on the impl (i.e., the macro author write impl<$FOO> Drop). This is already a sort of unhygienic thing to do, because there is no nesting of namespaces here.

I think all i'm saying here is that using indices feels like overkill, if we want to take this approach we should just adopt it now, but plan on replacing it with attributes on the type parameters themselves. If for some reason we can't make that possible, then we can debate the hygiene question.

@nikomatsakis
Copy link
Contributor

@pnkfelix

The question: Is <T: ?Alive> a reasonable way of stating "data of type T may not be valid/well-formed" ?

We said this on IRC, but I wanted to write it down for the record. When I last entertained the idea of ?Alive, I hit similar problems. I think you can still make such an annotation scheme work, but it doesn't fall out naturally from the trait system. It'll require some extra check layered on top and extra reasoning. It may still be a good idea to leverage familiar notation nonetheless, much as the Drop trait leverages the familiarity of the trait system but requires a lot of special rules.

@Ericson2314
Copy link
Contributor

@pnkfelix Three things come to mind:

  • The definition of Size must also have an ?Alive anti-bound or else applying potentially-dead T's fails to satisfy the restrictions on the type. I see this as analogous to these: https://play.rust-lang.org/

  • The elaborated 'a bound actually gets us what we want, by "out-constricting" the anonymous lifetime associated with the outer ?Alive (i.e. GLB of 'a that anonymous one is 'a).

    struct Pair<T: ?Alive>(T, fn(&T) -> String);
    struct Pair<T: ?Alive>(T, for<'a> fn(&'a T + 'a) -> String); // elaborates to this, right?
    
    impl<T: ?Alive> Pair<T> {
        fn call_it(&self) {
            println!("Pair says {}", self.1(&self.0));
        }
    }
  • For a Pair of a probably-dead T, and function which can takes a reference to a probably dead T (what we don't want for your use, but might be useful elsewhere), do one of

    struct Pair<'a: ?Alive, T: 'a>(T, fn(&'a  T) -> String);
    struct Pair<T: ?Alive>(T, for<'a: ?Alive> fn(&'a T) -> String);
    struct Pair<T: ?Alive>(T, for<'a: ?Alive> fn(&'a T + 'a) -> String);

@glaebhoerl
Copy link
Contributor

Is there any connection between ?Alive and the 'unsafe lifetime idea that was sometimes discussed a long time ago?

@Ericson2314
Copy link
Contributor

@glaebhoerl don't remember that / wasn't there for it, but going by the name, it might be related.

As I understand it:

  • for<'a, T: 'a> ...: Give me a lifetime 'a encompassing the call-site, and type T that outlives 'a
  • for<'a: ?Alive, T: 'a> ...: Give me any lifetime 'a, and type T that outlives 'a
  • for<T: ?Alive> ... == for<'a: ?Alive, T: 'a> ...

Because lifetimes are erased, it is fine to instantiate all 'a: ?Alive with any lifetime. 'unsafe could an bottom lifetime, which will fulfill 'a: ?Alive obligations while imposing no restrictions on T, but isn't safe to use otherwise.

@nikomatsakis
Copy link
Contributor

@pnkfelix had a thought I wanted to jot down. Assuming we opt for an attribute here like #[parametric] or #[eyepatch] or something, I think we should not put unsafe in the attribute name. Rather, we should require that the impl be declared unsafe iff it contains a #[parametric] attribute on one of its parameters.

@pnkfelix
Copy link
Member Author

pnkfelix commented May 18, 2016

So I finally had a chance to sit down and work on this some more.

I have prototyped a new version that still uses attributes, but attaches them directly to the generic type parameter, like so:

unsafe impl<#[may_dangle] X, Y: fmt::Debug> Drop for Foo<X, Y> {
    fn drop(&mut self) {   /* cannot access contents of `X`, but may access contents of `Y` */  }
}

(Well, its almost at the point of the above; I haven't implemented the part where it requires an unsafe impl yet. Once I get to that I'll revise the RFC PR and put up the code in a branch somewhere.)

(where `ARG` is either a formal lifetime parameter `'a` or a formal
type parmeter `T`).
@pnkfelix
Copy link
Member Author

pnkfelix commented Jun 2, 2016

TODO: the new kind of attribute (on generic parameter binding sites) should have its own, separate, feature gate.

@RalfJung
Copy link
Member

Is there any fundamental reason that this needs to be handled via an attribute as opposed to an additional bound? As in, impl<#[MayDangle] T> Drop for ... { fn drop(&mut self) { ... }} vs. impl<T: MayDangle> Drop ...?

I am not sure if "parametric" is the right term here. Parametricity is a relational property, i.e. it is expressed in terms of how Drop<T> and Drop<U> relate (assuming of course some relationship between U and T). It is my interpretation that the additional restrictions placed on drop here are not relational in nature; they are properties of the form "drop may not assume that some particular type / lifetime outlives this function".
Such "unary" properties (expressed by looking at a single execution of drop) are typically way less subtle than relational properties, so if at all possible, we should try to avoid relational reasoning.

@Ericson2314
Copy link
Contributor

This is still supposed to be a temporary fix, right?

@pnkfelix
Copy link
Member Author

@RalfJung my take is that when its a local assertion where the programmer is responsible for checking the correctness w.r.t. the actual implementation of the fn drop method, then either an attribute or a ?Bound (such as T: ?Live or even T: ?Dangle) is the right way to denote that.

  • There is nothing fundamentally stopping us from encoding this attribute via a <T: MayDangle> bound, but I think that would be an abuse of the bound system.
  • This is not a restriction on the potential values of the type in question. It is making an assertion that broadens the scope of such values. That's why I'm saying a T: Bound is wrong while a T: ?Bound would be fine.

Then I am left with a choice between #[attribute] T or T: ?Bound.

Why do I prefer an attribute for right now?

  • I would prefer to reserve the ?Bound syntax for some future point where we actually check the body of fn drop so that everything it calls also imposes the <T: ?Live> restriction on the type parameter T.
  • The use of an attribute reflects that this is a temporary, short-term fix to allow us to make progress with allocator-aware APIs in the stdlib.

@pnkfelix
Copy link
Member Author

@Ericson2314 : yes, this is entirely meant to be temporary. Hopefully we can independently work on a ?Bound-based solution. But I want to unblock the allocator-aware collections API work in the meantime.

@RalfJung
Copy link
Member

This is not a restriction on the potential values of the type in question. It is making an assertion that broadens the scope of such values. That's why I'm saying a T: Bound is wrong while a T: ?Bound would be fine.

I agree.

The use of an attribute reflects that this is a temporary, short-term fix to allow us to make progress with allocator-aware APIs in the stdlib.

Thanks. Maybe the temporary nature of this attribute should be stated in the RFC?

@aturon aturon added final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. and removed I-nominated labels Jun 27, 2016
@aturon
Copy link
Member

aturon commented Jun 27, 2016

The lang team is bringing this RFC into its final comment period 💬

To reiterate some of the recent discussion, the proposal in this RFC is intended as a temporary solution (along the lines of #[fundamental] and will not be stabilized as-is. Instead, we anticipate a more comprehensive approach to be proposed in a follow-up RFC. In the meantime, moving forward on this temporary fix will give us the tools we need in std to start adding custom allocator support.

@pnkfelix
Copy link
Member Author

Huzzah! The @rust-lang/lang team has decided to accept this RFC.

Again we stress that the solution described here is temporary and will not be stabilized as-is.

@pnkfelix pnkfelix merged commit 246631c into rust-lang:master Jul 11, 2016
@pnkfelix pnkfelix mentioned this pull request Sep 1, 2016
@alexreg
Copy link

alexreg commented Feb 14, 2018

Can anyone explain in layman's terms why this (or the previous "blunt attribute") is needed? I don't think the RFC properly explains that.

@glaebhoerl
Copy link
Contributor

I'm not sure it counts as a layman's explanation, but there was some really good discussion of this and related issues in this internals thread, and the other discussions linked from there. In particular this comment by @RalfJung, and the ensuing discussion about drop-desugarings, were the first time I felt like I had gotten an idea of what all this is fundamentally about.

@alexreg
Copy link

alexreg commented Feb 15, 2018

@glaebhoerl Appreciate the links, but I still don't get the basic idea I'm afraid. Why do we want to allow "dead" types and lifetimes? What can we then do with them? And why specifically in a drop fn? All these questions should be answered in the RFC, if you ask me. A motivating example would help too.

@Centril Centril added A-typesystem Type system related proposals & ideas A-drop Proposals relating to the Drop trait or drop semantics A-parametricity Proposals relating to parametricity. labels Nov 23, 2018
@pnkfelix
Copy link
Member Author

if anyone is reading the dialogue above and is similarly confused about the underlying motivation, I think the previous RFCs did dedicate significant space to explaining why you want to allow types with dangling references.

First, the original Sound Generic Drop RFC (RFC 769) explained why some destructors would access borrowed data:

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

The same RFC noted, as part of its drawbacks, that it did not include a way for a type to specify explicitly (as part of its API) that its borrowed data was not accessed from its destructor:

https://github.com/rust-lang/rfcs/blob/master/text/0769-sound-generic-drop.md#drawbacks

Then, the followup Non-Parametric Drop Check RFC (RFC 1238) explained why some types should allow one to assert that their borrowed data is never accessed from their destructors:

https://github.com/rust-lang/rfcs/blob/master/text/1238-nonparametric-dropck.md#why-we-need-an-escape-hatch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-drop Proposals relating to the Drop trait or drop semantics A-parametricity Proposals relating to parametricity. A-typesystem Type system related proposals & ideas final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.