Introduction: How'd you do that ?>
If you're coming from a language like Python or JavaScript, you're used to not worrying about memory. When you're done with a value, the garbage collector quietly comes in and frees that memory for you. It’s automatic, and most of the time, it just works.
So now you're probably thinking: How does a low-level, statically typed language like Rust manage to do the same — but without a garbage collector?
The answer lies in two things: RAII and strict scope management.
RAII stands for Resource Acquisition Is Initialization — which is just a fancy way of saying:
When an object is created, it also acquires any resources it needs (like memory), and when it goes out of scope, those resources are released automatically.
In C, you'd need to remember to free()
that memory yourself. Forgetting to do so causes leaks or worse. But in Rust, this cleanup is done for you — automatically and safely — using a special feature called the Drop
trait.
What is the Drop
trait?
In simple terms, Drop
is a trait that tells Rust what to do when a value goes out of scope and needs to be cleaned up.
Whenever you call drop(x)
, or a variable simply goes out of scope at the end of a function or block, Rust runs the code defined in that value's Drop
implementation.
This is what allows Rust to safely deallocate memory and other resources without a garbage collector.
The best part? Most types in the standard library — like String
, Vec
, HashMap
, and even primitives — already implement Drop
. But you can also implement it yourself for your own types, and that opens up a world of powerful patterns for resource management, logging, cleanup, and more.
How does Drop work?
You can already see the possibilities, but before diving into practical usage, it’s good to understand how this feature works under the hood. Let’s get into it.
When a variable goes out of scope, Rust runs the code you've written in your Drop
trait implementation. This lets you do things like gracefully shut down background tasks, persist data to disk, handle system signals, or just clean up resources in a predictable way.
But it doesn't stop there.
You might be wondering: "Does Rust also free the memory after running my drop()
?" And the answer is: No — you don’t need to. Rust does the hard part for you.
After running your custom Drop
code (if you implemented one), Rust will then automatically call drop()
all the fields of your struct, in reverse order of declaration. So if you have:
struct MyStruct {
first: i32,
second: i32,
third: i32,
}
Rust will drop third
first, then second
, and finally first
.
Want to test it? Add a
println!()
orthread::sleep()
in each field’s drop and observe the order. You just got your first assignment.
Since most native and primitive types already implement Drop
properly — freeing memory, closing file handles, releasing OS resources — you usually don’t need to worry about this yourself.
Why should we even use Drop
?
Nope, you’re not at your destination yet. Kidding — but seriously, if Rust already does cleanup for you, why should you ever care about implementing Drop
yourself?
Well, there are roughly 32 and a half good reasons to use Drop
. But broadly speaking, it’s useful anytime you want to do extra work before something gets cleaned up.
For example, say you had a Logger
struct that lives for the entire duration of your program. When it drops, you might want to persist the logs to disk. That’s something you can do inside a Drop
implementation — without needing to remember to call a manual shutdown function.
I've actually done something similar in a project called Jomify. Not identical, but the same principle.
The Drop
trait is used all over Rust’s standard library:
Unlocking mutexes automatically
Cleaning up network requests in HTTP libraries
Ensuring graceful shutdown in web frameworks
And most importantly: debugging and logging
Imagine a server that panics unexpectedly. You have a custom database handle, and if it drops with some internal state like state = 0
, that could indicate a problem. With a Drop
impl, you can log that state automatically the moment the handle goes out of scope — without needing any extra logic in your main code path.
You could even hook into a logging system like AWS CloudWatch, ELK Stack, or any log aggregation tool, and use an EventBridge rule to trigger alerts or Lambda functions when specific drop-time log entries appear. This turns drop events into powerful infrastructure signals — especially for debugging or triggering compensating actions.
That’s really cool.
How do we actually implement the Drop
trait?
Just like any other trait.
You write impl Drop for YourType,
and then define the one required function: fn drop(&mut self).
Here’s a basic example:
struct ExampleStruct;
impl Drop for ExampleStruct {
fn drop(&mut self) {
println!("Dropping here");
}
}
That’s it. As soon as ExampleStruct
goes out of scope, your drop()
code runs.
Maybe you shouldn't implement Drop
; Some caveats
As simple as it looks, there are a few things to watch out for when writing a Drop
impl:
Don't implement Drop for types that derive
Copy
Rust simply forbids this. Not for technical reasons, but to avoid ambiguity. Imagine having two identical copies of a value — which one shouldDrop
run on? Rust refuses to even entertain that scenario. No double funerals for cloned identities.Don't implement generic
Drop<T>
Rust doesn’t allow genericDrop
impls likeimpl<T> Drop for MyStruct<T>.
This is due to monomorphization conflicts. You have to be specific — e.g.,impl Drop for MyStruct<String>.
Yes, that means repeating yourself for every version you care about.Don't panic in a
drop()
function Please don’t. Panicking duringdrop()
is like screaming in a cab while the driver is parking — chaotic and unnecessary. It could crash the whole program or trigger other weird behavior.Don't call
.drop()
manually — usestd::mem::drop()
If you want to drop a value early, don’t call itsdrop()
method directly. Rust won’t let you anyway. Instead, usestd::mem::drop(value).
This safely transfers ownership, runs theDrop
trait, and prevents further use of the value.It’s worth noting:
drop(&mut self)
takes a mutable reference, which guarantees you can’t accidentally drop the same value twice. That’s also why Rust doesn’t allowDrop + Copy
— it would break this very promise.
In summary, it’s just another hook
You could rename Drop
to Cleanup,
and that would make perfect sense. It's essentially a hook into Rust’s automated cleanup process. Rust handles the hard memory work — and you get a hook to do extra logic when something is about to be cleaned up.
On advanced notes:
Rust currently doesn’t support async
Drop,
though it’s being discussed.You can bypass
Drop
using tools likestd::mem::forget()
orManuallyDrop.
If you're working with raw pointers,
unsafe
blocks, or custom allocators, then the responsibility to clean things up falls on you — andDrop
becomes a powerful tool in your belt.
Want to Read More?
If you’re curious or just want to dive a bit deeper into how Drop really works behind the scenes, check these sources out:
The Rust Book – Drop – Straightforward explanation from the official Rust book.
The Rustonomicon – Drop – Talks about the unsafe and low-level behavior of Drop, useful if you want to know what really happens.
std::mem::drop – The actual drop function you use when you want to manually drop a value.
ManuallyDrop – For cases where you want to control exactly when things get cleaned up (or not).
RFC 3201 – Async Drop – Talks about the idea of making Drop support async. Not in Rust yet, but might be in the future.
You do not have to read them, but feel free to share what you learn! I guess that is a wrap for now, see you next week.