#[derive(Debug)]
struct Foo;
impl Drop for Foo {
fn drop(&mut self) {
println!("Dropping Foo!")
}
}
How does Rust release resources in drop? Take a look at the code above. There is no resource release code inside the drop function. Rust has a rule that says when ownership leaves the scope, drop will be called to release resources. However, even though drop is called, where are the resources being released? I am very confused.
If it is required for programmers to manually write resource release code inside drop and resources are released when ownership is gone, how is this different from C++ destructors?
If the standard library provides everything to users, and the drop functions for its components already have resource release code written, can we trust that everything built on top of the standard library will safely release resources without any issues? Absolutely foolproof?
I am unsure about how drop actually releases resources. I have heard that the Rust compiler takes care of it for us, but isn't that too much of a handoff of responsibility?
What if my code does not use the standard library? In such cases, can resource release still be successful? How is this ensured?
Rust's drop works the same fashion that C++ destructors do, yes. Rust doesn't provide an absolute guarantee that all resources are cleaned up, either; it's absolutely possible to create an Arc cycle or call Box::leak to get a dynamic allocation that won't be cleaned up.
Unless you're doing manual dynamic resource allocation, you shouldn't ever need to write explicit Drop implementations for resource deallocation. All fields of a structure are dropped after the containing structure is dropped. (This process is often referred to as drop glue.)
So, in Rust, resource deallocation is actually all done manually?
In that case, how do I know which resources need to be deallocated and which ones don't? Also, I'm worried about manually deallocating resources. What if I deallocate them multiple times, even though the compiler should prevent that.
At the end of a scope, values that have a Drop impl have their Drop::drop function called in reverse order of declaration.
Directly after a value's Drop::drop (or if it doesn't have one, directly after where it would have been called), any members of the value that have a Drop impl have their Drop::drop function called.
This means that if you make a struct composed of someone else's types, you can be sure that as long as they wrote correct Drop impls, your type will be correctly dropped. Rust also doesn't let you call Drop::drop manually, so it's extremely difficult and would require unsafe to drop something multiple times.
It is generally advised to make specific types for each kind of deallocation you want to do. For heap memory deallocation, Box already handles that. If you need to do something exotic with heap memory, usually you'll allocate a Box in the constructor, leak it, and then turn it back into Box in Drop.
If you write unsafe code that allocates a resource, for example, using an FFI call, then you'll have to write (usually unsafe) code that deallocates the resource, normally by implementing Drop for a type that wraps the resource. In all other cases you'll be using safe Rust abstractions that implement resource allocation and deallocation themselves, and you don't have to do anything.
Most programmers will use other types like Box or Vec and not have to worry about double deallocation and the like. The other type's destructors do the deallocation.
You can't manually deallocate without unsafe, which entails (very generally speaking) opting out of the compiler protecting you. Using unsafe is telling the compiler to trust you to get it right.
Perhaps one thing you're not aware of is that Rust and C++ use the same concept for automatic resource allocation and deallocation, RAII. Rust does it somewhat differently than C++, but it is the same idea.
One more question. Copy types conflict with Drop. In this case, if we wrap a general i32 type in a Box, like Box::new(3), will this prevent the copy type from being released? If we create a thousand instances of this, will it lead to memory consumption, waste memory, and be unable to eliminate it?
So can I understand it like this? Rust and C++ are similar. Rust does not have so-called automatic resource release. In fact, the premise of resource release is that you have already written the code to release the resources in your drop method or destructor. The difference from C++ is that C++ requires manual calls to delete to release resources, whereas Rust does not require you to manually release resources. Instead, it relies on the disappearance of ownership to automatically call drop and execute the resource release code you have written in advance. This resource release is also done recursively. For example, if your struct has n fields, first the struct calls its own drop, which in turn calls drop on all the n fields of the struct to release the resources.
So rather than saying Rust will automatically release resources for you, it's more accurate to say that programmers need to write resource release code in advance. Rust relies on its ownership system to automatically call drop and release resources when the ownership of a variable disappears.
No, both Rust and C++ have automatic resource release/deallocation. This is accomplished using RAII, which is the model described in the Destructors link that @quinedot sent and the Wikipedia link I sent. In C++ you do not have to call delete, because you almost always use a smart pointer instead, which automatically calls delete when it is dropped.
Just because someone has to implement Drop doesn't mean it isn't automatic. This is like many things in Rust and C++: they are not built into the language for all types, so you may have to implement a trait (Drop in this case). But once the trait is implemented they are enforced by the language automatically.
That is true, except when you say "programmers need to write resource release code" the key thing is that very few programmers have to do this because it is very uncommon to allocate resources directly using an FFI call. For almost everyone, all of the resources you need from OS are already provided via a safe abstraction that implements Drop. The main exception are programmers who are writing embedded apps or OS drivers.
Another viewpoint is to say that Rust provide automatic resource management, and this resource management is extensible: for resources not already managed by the Rust std library, there is a way (implementing Drop) for programmers to extend it to manage other types of resources.
I should add that the programmer also needs to implement drop when writing unsafe code that manages object ownership. For example, in some case, such code will allocate memory directly rather than using a Box or Vec. In that case, the wrapper type must implement Drop to deallocate the memory. So this situation is in addition to the OS drivers and embedded apps that I mentioned above. It is still quite uncommon.
Right, I think you're referring to this Copy and Drop are exclusive section. This is really saying that when a type implements Copy, that it never makes sense to implement Drop. That doesn't mean that a Copy type like usize that is wrapped in a Box causes a problem. The Box is dropped to deallocate its memory, but the usize inside is not dropped because it is not the resource that was allocated. The Box owns the allocated memory, not the usize.
This variable is allocated on the stack. So when the stack over, the memory for this number is also deallocated. However, if this number is allocated on the heap, then the memory management is manual. Since i32 is a Copy type, if you manually call the drop function, it doesn't actually move the value of i32 into the function, which would not result in the ownership being lost. Instead, a copy of the data is made, not a move. This also means that the memory for i32 cannot be freed if it is on the heap. isn't it?
Copy types cannot also implement Drop, but that does not mean they cannot be dropped; it means that they cannot run some code when dropped. The owner of a value is free to drop it and then deallocate the memory that held that value, whether or not the type of that value implements Copy and whether or not it implements Drop.
No... if we're speaking the same language. There's no memory leak.
Box is not Copy, implements Drop, and handles deallocating the memory the integer was stored in. Probably it uses drop_in_place to invoke the contained type's destructor (which may or may not involve Drop, and could be a no-op). And then deallocats the memory. But I'm on mobile and am not going to look it up right now .
Pro tip though: Vec is probably a better implementation to look into, because Box is magic / has special compiler support. You can move out of a box so if might be uninitialized. I.e. it may be hard to see the exact behavior of magical types.