I propose we specify and stabilize drop order in Rust, instead of treating it as an implementation detail. The stable drop order should be based on the current implementation. This results in avoiding breakage and still allows alternative, opt-in, drop orders to be introduced in the future.
The current drop order seems counter-intuitive (fields are dropped in FIFO order instead of LIFO), but changing it would inevitably result in breakage. There have been cases in the recent past when code broke because of people relying on unspecified behavior (see for instance the post about struct field reorderings). It is highly probable that similar breakage would result from changes to the drop order. See for instance, the comment from @sfackler, which reflects the problems that would arise:
Real code in the wild does rely on the current drop order, including rust-openssl, and there is no upgrade path if we reverse it. Old versions of the libraries will be subtly broken when compiled with new rustc, and new versions of the libraries will be broken when compiled with old rustc.
Introducing a new drop order without breaking things would require figuring out how to:
- Forbid an old compiler (with the old drop order) from compiling recent Rust code (which could rely on the new drop order).
- Let the new compiler (with the new drop order) recognize old Rust code (which could rely on the old drop order). This way it could choose to either: (a) fail to compile; or (b) compile using the old drop order.
Both requirements seem quite difficult to meet and would result in additional complexity. In my opinion, it would be better to accept the current non-intuitive drop order than to introduce more complexity.
Finally, in case people really dislike the current drop order, it is still possible to introduce alternative, opt-in, drop orders in a backwards compatible way. However, that is not covered in this RFC.
The design is the same as currently implemented in rustc: struct fields and tuple elements are dropped in a FIFO order. This is also valid for enum variants of both kinds.
How We Teach This
When mentioning destructors in the Rust book, Reference and other documentation, we should also mention that they are run in a particular order.
- The counter-intuitive drop order is here to stay.
- Figure out how to let rustc know the language version targeted by a given program. This way we could introduce a new drop order without breaking code.
- Introduce a new drop order anyway and try to minimize breakage by running crater and giving the community enough time to update.
To which extent should we specify drop order? For instance, should drop order of closure captures be specified? Or should we limit ourselves to structs, tuples and enums?