Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
199: Implementing Unsafe Traits
Imagine you're a building inspector. Usually, the city has a strict code that automatically rejects any blueprint that doesn't meet specific safety standards. But sometimes, a world-class architect comes along with a revolutionary design that doesn't fit the standard code, even though it's actually safer than the traditional way. To get that building approved, the architect doesn't just ask for a favor; they sign a legal affidavit. They are essentially saying, "I have personally verified that this structure is sound, and I take full legal responsibility if it collapses."
Implementing an unsafe trait is exactly like signing that affidavit. In most of Rust, the compiler is the inspector, checking every line of your code. But with unsafe traits, the compiler steps back and says, "I can't prove this is safe, so I'm going to let you decide. If you tell me it's safe, I'll trust you—but if you're wrong, the resulting memory corruption or data race is entirely on your head."
The Handshake Between You and the Compiler
Normally, when you implement a trait, you're just providing some functionality. But when a trait is marked as unsafe, the act of implementing it is what's dangerous. This is different from calling an unsafe fn. With an unsafe function, you're saying "this specific call is safe." With an unsafe trait, you're making a permanent claim about the nature of your type.
The syntax reflects this gravity. You don't just write impl Trait for Type; you have to write unsafe impl Trait for Type. That unsafe keyword is your signature on the affidavit.
Dealing with the Heavy Hitters: Send and Sync
You've probably encountered Send and Sync before. They are the most common unsafe traits in the ecosystem. Most of the time, the compiler implements these for you automatically if all the members of your struct are also Send or Sync. But what happens when you're dealing with raw pointers?
Raw pointers (*mut T and *const T) are explicitly !Send and !Sync because Rust has no way of knowing if the memory they point to is managed in a thread-safe way. But if you're wrapping a C library that you know is thread-safe, you'll find yourself stuck. The compiler will refuse to let you move your wrapper across thread boundaries.
struct MyCWrapper {
handle: *mut libc::c_void,
}
// This will fail if we try to pass it to std::thread::spawn
// because MyCWrapper is !Send.
To fix this, you have to step in and tell Rust that your wrapper is safe to move between threads:
unsafe impl Send for MyCWrapper {}
unsafe impl Sync for MyCWrapper {}
I want to be clear: there is no code inside those curly braces. You aren't "implementing" a method. You are simply asserting a property about your type. If MyCWrapper actually points to some non-thread-safe global state in a C library, you've just introduced a subtle, nightmare-inducing data race into your program.
When the Compiler Gets Too Nervous
You might wonder why we don't just make everything Send. The reason is that the compiler is conservative for a good reason. If you have a type that relies on thread-local storage, moving it to another thread would cause a crash or undefined behavior. By making Send an unsafe trait, Rust ensures that the human developer—the only one who knows the external context of the data—is the one making the call.
When I'm reviewing code and I see unsafe impl, it's the first place I look. I don't look for the code; I look for the justification. If you're writing this in a real project, always leave a comment above the implementation explaining exactly why the trait's invariants are upheld. Future you (and your teammates) will thank you when you're debugging a segfault six months from now.
📋 Practical Task
Implementing Thread-Safety for a Raw Pointer Wrapper
You are building a wrapper for a legacy C-API that manages a shared configuration object. The C-API documentation guarantees that the configuration object is read-only and can be safely accessed from any number of threads simultaneously. However, because your wrapper uses a raw pointer, Rust is treating it as !Send and !Sync.
Your Task:
Complete the code below. Create a struct named ConfigHandle that wraps a *const u8. Then, implement the necessary unsafe traits to allow ConfigHandle to be sent across threads and shared between threads. Finally, demonstrate this by moving the handle into a new thread using std::thread::spawn.
use std::thread;
struct ConfigHandle {
ptr: *const u8,
}
// TODO: Implement the unsafe traits here to make ConfigHandle
// Send and Sync.
fn main() {
let handle = ConfigHandle { ptr: std::ptr::null() };
// This will only compile if you've correctly implemented the unsafe traits
let thread_handle = thread::spawn(move || {
println!("Successfully moved the handle to a new thread!");
});
thread_handle.join().unwrap();
}
There are no comments for now.