- Feature Name:
implicit_drop
- Start Date: 2021-07-22
- RFC PR: rust-lang/rfcs#0000
- Rust Issue: rust-lang/rust#0000
Summary
A trait for types whose values may not be dropped automatically and therefore require additional attention.
Motivation
- Additional of this trait allows library authors to force users to think about concrete uses of certain values they are provided with.
- The intension is to add absolutely minimal support for linear types.
- Make working with such values not too complicated via mechanism.
Guide-level explanation
The trait is defined as follows:
pub auto trait ImplicitDrop {}
This is an auto trait, built in into the language.
It is implied bound for all generic parameters, like Sized
.
The syntax which is used to opt out from it is following: ?ImplicitDrop
.
The trait is here to allow types to require explicit disposing and thus making code more readable and safe.
Essentially this is hard #[must_use]
lint on the type level. Supposed to be used in cases where accidental drop of a value may cause undesired behavior.
Example:
enum TransactionState {
Clean,
InProgress,
}
struct TransactionGuard<`c,const STATE: TransactionState>
{..}
impl<`c> !ImplicitDrop for TransactionGuard<`c,{TransactionState::InProgress}> {};
fn example() {
//...
let mut handle = handle.begin(); // yields "transaction in progress type";
match data {
Some(data) => {
//...
handle.commit(); //value consumed, all is ok.
},
None => {
//...but here we also have to finalize transaction
// if we comment the next line the code will fail to compile
handle.abort();
}
}
}
Note, the trait on its own has nothing to do with finalization, etc. This means that desired ways of consuming value are determined separately, and most probably going to depend on concrete contexts, etc.
Finalizer methods
New attribute #[finalize]
is introduced. It marks a method as final destination of a value. It requires an implementor type to be Drop
and annotated method to take self
by value. Drop
type are allowed to be destructed only inside #[finalize]
methods. These methods can have return types.
impl<`c> TransactionGuard<`c,TransactionState::InProgress> {
#[finalize]
fn commit(self) -> Result<(),Error> {...};
#[finalize]
fn abort(self) {...};
}
Important detail is that if finalizer methods is not in scope (and not inherent), it's not required to be called, instead drop
is considered as the least possible finalization method. The main reason of doing so is that we don't run in scoping problems and avoid concerns about the default finalization way.
Reference-level explanation
pub auto trait ImplicitDrop {}
The opt out bound ?ImplicitDrop
disables calling a destructor on leaving a scope for values of some bound type. Such value going out of scope results in an error.
So the following will not compile:
fn impl_show<T: ?ImplicitDrop>(arg: T) {
say_hello();
//error: cannot implicitly drop function's argument
//suggestion: add `drop(arg);`
}
The reason of doing the trait in such way is that this will require no changes in existing code:
- All types in the language will automatically get it implemented;
- It's opt-out bound like
Sized
. - It requires adding support for such types on per container basis. But in most cases one is already calling
drop
(ordrop_in_place
) manually, or adding such a support is one line level change (thanks todrop
).
Finalization
We add #[finalize]
attribute for methods.
It allows to destructure the type regardless of whether it implements Drop
or not.
Using this attribute implies and requires Self
to be !ImplicitDrop
.
Implementation
There are two approaches on how to implement this:
- We compile code as usual, but when MIR tries to insert
drop
call for a!ImplicitDrop
value we error out. - We do CFG (dataflow) analysis, during it we determine which values go out of scope and check whether we can implicitly drop any.
Drawbacks
The worst thing about this proposal is that it requires library support for !ImplicitDrop
types.
Rationale and alternatives
- I've considered other variants for finalization: however, making
#[finalize]
to actually implement a trait leads to:- Need to include these traits in prelude => edition
-
Fn
-like traits => burden
- Also, there are multiple approaches to adding linear types:
- Make
T: Drop
to actually make some sense => not backward compatible -
Forget
auto-trait on its own => only opens a way for linear types, but doesn't assist in using them.
- Make
Prior art
Unresolved questions
- The merit of the proposal.