struct Dump(Rc<i32>);
impl Drop for Dump{
fn drop(&mut self) {
println!("destroy");
}
}
fn main(){
let d = Dump(Rc::new(1));
} // { let mut d = d; d.drop()}
As exposed by the pseudo-code in the comment, the compiler would insert such a code to destroy the variable d at the end of the scope. The behavior is that destroy is printed only once. If we explicitly drop d, such as
fn main(){
let d = Dump(Rc::new(1));
drop(d);
println!("main");
}
The destroy is printed once prior to main, this means the compiler does not invoke the destructor at the end of the scope. With this behavior, I suppose that the explicit drop(d) can suppress the compiler from inserting the destroy operation for d at the end of the scope. However, consider this example
use std::future::Future;
fn spawn<F:Future>(f:F) where F:Send + 'static{}
fn show(){
spawn(async move{
let d = Dump(Rc::new(1));
drop(d);
async {}.await;
});
}
The compiler says
future is not Send as this value is used across an await
^^^^^^ await occurs here, with d maybe used later
The variable d has been dropped and moved, how could it be used after the await point? The error seems to arise from that the compiler still inserts the drop operation at the end of the scope, which results in the use of d after the await point.
So, I wonder, does the explicit drop operation suppress the implicit drop operation(that uses the variable) inserted by the compiler? Otherwise, how does the compiler solve the double-free?
Yes, if you move a value out of a variable, then the implicit drop is inhibited. The drop method moves the value out of the variable.
As for the example with .await, that's a problem in how futures are analyzed. The compiler is too strict here, and it would not be incorrect for the compiler to accept the code.
The red and green code is equivalent, but the compiler (currently) will only be satisfied if you use the block to drop the variable. Also see issue #104883 in Rust's bug tracker.
Technically the drop function isn't special. It's just a function that takes an argument by value and does nothing with it. The compiler just inserts the drop related code that would have gone at the end of the variable's scope into the drop function instead.
Yes, I know that drop function works based on the ownership guaranteed by Rust. More generally speaking, The question is: does the move operation(i.e the variable will lose the ownership) inhibit the compiler from inserting implicitly drop operation that would have been inserted at the end of the scope in which the variable is defined?
let other = x; // x is moved
other_2 = x2; // x2 is moved
All these move operations will make the variables lose their ownership, is the compiler inhibited to insert the drop operation for x, and x2 at the end of the scope?
I'm not entirely sure I understand what you're trying to ask. One of the primary goals of the ownership system is to ensure safe code can't produce "double drops" where one value gets dropped twice.
A variable which has been moved out of cannot be dropped. If the compiler can't know statically whether a variable has been moved out of (for example because it may be moved out of in a conditional block) then the compiler adds a drop flag so the generated drop code can be skipped for that variable.
I asked how the compiler knows whether it should insert the implicit drop operation for a moved variable. Then you give the answers that the compiler uses the drop flag, which is what I want to know.
So, the last issue is, in the latter case(the async function), the compiler already knows the variable d is dropped before the await point, i.e., there is no further drop operation to be inserted after the await operation, why does the compiler say we use the d after the await point?
Hmm I think what's happening there is that the variable is still considered to exist in that scope, even though it's been moved out of. If you use an extra block to scope the variable, it works
When an initializedvariable or temporary goes out of scope, its destructor is run, or it is dropped. Assignment also runs the destructor of its left-hand operand, if it's initialized. If a variable has been partially initialized, only its initialized fields are dropped.
Incidentally, here's another issue with some interesting notes on drop elaboration. It's about drops in const but the async issues are related -- more drop information is needed to improve things, but currently that information comes from a later pass, and getting the approximation wrong results in miscompilations (sometimes even unsound ones).