Destructuring structs which implement Drop


#1

I have wondered why you can’t destructure structs with destructor.

The idea would be that moving out of a struct prevents the struct’s destructor from being called and gets all the struct’s elements in the local namespace (or the individual elements get dropped when they’re not being matched).

I had a discussion about that in #rust, where people said that this could be unsafe in some cases. But in my opinion, destructuring a struct cannot cause more unsafety than being able to construct it in the first place

That means, privacy of the right elements prevent the struct from being unsafely destructured outside the owning module which already knows its inner workings.


#2

Can you expand on why you think not having the destructor run is not more unsafe than constructing? I’m not sure I follow there (Note: I didn’t see the IRC discussion)


#3

As an example, we talked about file handles. They shouldn’t leak, so we build a struct for them, like

struct File {
    file_desc: uint,
}

If we destructure this struct, we do lose the destruction, thus we have to clean it up ourselves. But in my opinion, this is no different from the unsafety that arises when we could arbitrarily construct the struct, as we could construct it without owning the relevant resources, like in this case the file handle. This would lead to even more unsafety than omitting the destructor, as we might accidently close a file descriptor that another thread owns.

Similarily for the builtin struct Vec:

struct Vec<T> {
    len: uint,
    cap: uint,
    ptr: *mut T,
}

When we destructure this (thus preventing the destructor from running, the only thing that’s bad is that we have to handle deallocation ourselves. If we however construct this with a pointer we don’t own, we again run into real problems.


#4

Ah, yes. I do see your point.

It might be that the construction case is considered more visible/explicit, while allowing someone to destructure a value and omitting the drop could be done by mistake?


#5

But you can’t actually create instances of structs that have private fields:

mod foo {
    #[deriving(Show)]
    pub struct Bar {
        data: uint,
    }

    impl Bar {
        pub fn new (i: uint) -> Bar {
            Bar { data: i }
        }
    }
}

fn main () {
    let bar = foo::Bar::new(1); // this works 
    // let bar = foo::Bar { data: 1 }; // this doesn't
    println!("{}", bar)
}

You’re expected to keep private any data members which could be used to violate safety guarantees, so it should never be possible to create a struct which can be manually instantiated with invalid values (if you can, you’re using unsafe wrong).

And it’s worth noting that the fields of the standard library’s Vec and File types are entirely private.


#6

We have the invariant right now that if a type has a destructor, it will always be run when that value is no longer accessible. Doing this change would break that invariant. There is the argument that if you want that, use a private field. It should also be noted that destructuring isn’t entirely disallowed, just consuming the value:

mod child {
    pub struct Foo {
        pub a: int,
        pub b: int
    }
    
    pub fn new() -> Foo { Foo { a: 1, b: 2 } }
    
    impl Drop for Foo {
        fn drop(&mut self) {
            println!("dropped")
        }
    }
}

fn main() {
    let x = child::new();
    let child::Foo { ref a, ref b } = x;
}

I think this is a non-problem, but I agree with your main point that destructuring and constructing are equally unsafe, in the face of an unsafe destructor.


#7

As you said, you could use a private field. In fact you probably should be using a private field, because the constructing the struct is probably unsafe too.

I proposed this change because it is quite handy for functions into_*, which is a pattern that Rust advocates.

One example of this is the Vec<T> struct, where the move_items functions could be simplified by this change.


#8

I think being able to destructure structs with a destructor would be pretty useful, mostly for the same into_* use case, but it also simplifies stuff like RAII operations that can be “disarmed” which wouldn’t need to carry an extra flag/layer of Option with themselves anymore.

Field access seems like a sufficient precondition to make sure it’s not any more unsafe than things you could do otherwise. We probably still want unsafe fields anyway…


#9

Once the drop flag is removed, this will be doable with a syntax extension that generates an equivalent struct without a drop implementation and some transmute magic.


#10

Why make a work-around for something that already works but is only explicitely disallowed?


#11

Earlier musings by me on the same subject: https://github.com/rust-lang/rust/issues/11855

(This seems like a completely backwards compatible loosening of a restriction, so I don’t think there’s any rush.)


#12

Note that this is a different idea: I think the struct shouldn’t get its destructor called when destructured, just like there isn’t a constructor called when you ‘create’ it.