Skip to content

Prohibit specialized drops #8142

Description

@mstewartgallus

UPDATE:

The plan for drops, as I recall, was to prohibit "specialized" drops, but this never got implemented. The idea is to require that impls of Drop meet special criteria such that the Selfparameter must (a) be a nominal type (struct or enum); (b) have fresh type parameters for each type parameter of the type with (c) no additional bounds beyond those declared in the type declaration. Note that we will have to permit type bounds in type declarations, as well, which is to some extent a separate issue (also required for DST).

For example, this is legal:

struct Foo<T> { ... }

impl<T> Drop for Foo<T> { ... }

but this is not

impl<T:Send> Drop for Foo<T> { ... }

nor the example below.

ORIGINAL:

The following code prints Dropping!. This is wrong.

struct Foo<T>;

#[unsafe_destructor]
impl Drop for Foo<bool> {
    fn drop(&self) {
        println("Dropping!")
    }
}

fn main() {
    let _foo: Foo<()> = Foo;
}

Activity

  1. glaebhoerl commented on Aug 1, 2013

    @glaebhoerl
    Contributor

    Is there a use case for this? C++ doesn't give you a way to do it without going to the trouble of template specializing the whole type (and I've never missed it). You could just prohibit writing a Drop impl that isn't of the form type-constructor-applied-to-zero-or-more-type-variables. (Further prior art is that standard Haskell enforces this restriction for /all/ type class instances, and requires the FlexibleInstances language extension to lift it.)

  2. mstewartgallus commented on Aug 1, 2013

    @mstewartgallus
    ContributorAuthor

    I'd be okay if this would be prohibited because this can always be worked around by adding flag variables to the value although this would be a neat feature. One use case that I was trying to use this for was to have a drop instance for an Attribute<Enabled> type that disables it, and not for the Attribute<Disabled> type (where Attribute refers to an OpenGL vertex attribute, and Enabled, and Disabled are phantom types.

  3. glaebhoerl commented on Aug 2, 2013

    @glaebhoerl
    Contributor

    Interesting, if it's not useless it might be worth keeping. :) There was also a discussion somewhere about what it means to have trait bounds on a Drop impl which seems vaguely related, but I can't find it...

  4. mstewartgallus commented on Aug 9, 2013

    @mstewartgallus
    ContributorAuthor

    This issue seems similar to issue #6971

  5. bluss commented on Aug 22, 2013

    @bluss
    Contributor

    your example Attribute<Disabled> makes me think of c_vec which has two modes, either it owns the buffer or it doesn't.

  6. alexcrichton commented on Dec 16, 2013

    @alexcrichton
    Member

    Nominating, this is a pretty serious problem.

  7. pnkfelix commented on Dec 19, 2013

    @pnkfelix
    Contributor

    accepted for "first major release", P-high.

  8. nikomatsakis commented on Dec 19, 2013

    @nikomatsakis
    Contributor

    Updated text to describe our planned fix.

  9. pcwalton commented on Jun 16, 2014

    @pcwalton
    Contributor

    I would perhaps like to fix this issue in the near-term by just putting #[unsafe_destructor] behind a feature gate.

  10. added a commit that references this issue on Jun 20, 2014
    dcbf4ec
  11. added a commit that references this issue on Jun 20, 2014
    2563481
  12. 11 remaining items

  13. pnkfelix commented on Feb 11, 2015

    @pnkfelix
    Contributor

    Ah, this appears to be a more thorough version of #21201.

    (Subtask of #8861)

  14. pnkfelix commented on Feb 12, 2015

    @pnkfelix
    Contributor

    This blocks removing the unsafe_destructor feature gate.

    calling 1.0 polish unless discussion of #22196 changes my opinion.

  15. added this to the 1.0 milestone on Feb 12, 2015
  16. pnkfelix commented on Mar 20, 2015

    @pnkfelix
    Contributor

    okay I think this is the last thing I think is blocking #22196, so while it is categorized as "1.0 polish", it really would be good to get it in for the beta. Looking at it now.

  17. self-assigned this
    on Mar 20, 2015
  18. pnkfelix commented on Mar 21, 2015

    @pnkfelix
    Contributor

    I have something plausible put together; the main thing remaining is dealing with the fallout of either adding or removing the appropriate bounds everywhere in libstd in order to placate the pass.

  19. pnkfelix commented on Mar 21, 2015

    @pnkfelix
    Contributor

    The pass I have put together seems to handle type-constraints properly, but it does not yet properly deal with region constraints.

    For a little while I was not sure if this would be a problem, but I did eventually come up with a test case illustrating why one must ensure region constraints are indeed dealt with properly:

    #![feature(unsafe_destructor)]
    
    use std::cell::Cell;
    
    struct InvalidateOnDrop {
        orig_value: &'static str,
        value: &'static str
    }
    
    // Note: Definition has no constraint relating 'b and 'a ...
    struct P<'c, 'b:'c, 'a> {
        x: &'c Cell<&'b InvalidateOnDrop>,
        y: &'a InvalidateOnDrop
    }
    
    // ... but the Drop impl says 'a outlives 'b, and thus is
    // able to copy `self.y` into `self.x`.  Havoc ensues below.
    #[unsafe_destructor]
    impl<'c, 'b:'c, 'a:'b> Drop for P<'c, 'b, 'a> {
        fn drop(&mut self) {
            self.x.set(self.y);
        }
    }
    
    #[allow(non_snake_case)]
    fn InvalidateOnDrop(s: &'static str) -> InvalidateOnDrop {
        InvalidateOnDrop {
            orig_value: s,
            value: s,
        }
    }
    
    impl Drop for InvalidateOnDrop {
        fn drop(&mut self) {
            self.value = "invalidated";
        }
    }
    
    struct Loud<'l1, 'l2:'l1> {
        name: &'static str,
        value: &'l1 Cell<&'l2 InvalidateOnDrop>
    }
    
    #[unsafe_destructor]
    impl<'l1, 'l2> Drop for Loud<'l1, 'l2> {
        fn drop(&mut self) {
            let orig = self.value.get().orig_value;
            let val = self.value.get().value;
            if orig == val {
                println!("dropping Loud {} pointing to {}", self.name, val);
            } else {
                println!("dropping Loud {} pointing to {} (orig: {})", self.name, val, orig);
    
            }
        }
    }
    
    
    
    fn main() {
        let b = InvalidateOnDrop("b");
        let c = Cell::new(&b);
        let _l1 = Loud { name: "l1", value: &c };
        let a = InvalidateOnDrop("a");
        let _p = P { x: &c, y: &a };
    
        let _l2 = Loud { name: "l2", value: &c };
        println!("Hello World");
    }

    playpen

    The above prints out (if you are lucky and other data corruption does not happen first):

    Hello World
    dropping Loud l2 pointing to b
    dropping Loud l1 pointing to invalidated (orig: a)
    

    thus illustrating that the Loud destructor observed a value in a post-drop state. badness.

  20. pnkfelix commented on Mar 21, 2015

    @pnkfelix
    Contributor

    ah sweet, I think I actually figured out how to do this (thank goodness I have compare_method.rs to look at).

    And it took me way less time that it did for me to come up with above example -- I just needed the motivation!

    (Update: spoke too soon: My check is rejecting too many programs. Time to read compare_method.rs more carefully.)

  21. added a commit that references this issue on Mar 24, 2015
  22. added 2 commits that reference this issue on Jan 13, 2022
  23. added a commit that references this issue on Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

A-destructorsArea: Destructors (`Drop`, …)P-mediumMedium priority

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions