Course contentsShow
← Rust
Lesson 1134 of 1,91010. Concurrency PrimitivesFree lesson

Implementing Custom Marker Traits

You will use unsafe blocks to opt-in types for thread-safe transfer when compiler guarantees are insufficient.

In this lesson, you will learn how to manually signal to the Rust compiler that your custom types are safe to share across thread boundaries when the default auto-derived guarantees are insufficient.

The Role of Marker Traits

As we have explored in our study of concurrency, Rust uses the Send and Sync marker traits to enforce memory safety. Send indicates a type can be moved across thread boundaries, and Sync indicates it is safe for multiple threads to hold a reference to the same instance. Usually, the compiler derives these automatically based on whether a type's fields are also Send or Sync.

However, when working with low-level concurrency primitives—such as FFI-wrapped pointers or custom atomic synchronization wrappers—the compiler may be too conservative. It sees an raw pointer and assumes "not thread-safe." You, as the developer, know better based on your implementation logic.

Implementing via Unsafe

To manually opt-in a type to these traits, you implement them for your struct. Because this bypasses the compiler's safety checks, the implementation itself must be marked as unsafe.

struct RawWrapper {
    ptr: *mut i32,
}

// We know that our specific logic ensures that 
// operations on this pointer are thread-safe.
unsafe impl Send for RawWrapper {}
unsafe impl Sync for RawWrapper {}

By adding unsafe impl Send for RawWrapper {}, you are making a formal contract with the compiler. You are asserting that all internal state within RawWrapper satisfies the concurrency requirements. If your logic is flawed and a data race occurs, you have violated the guarantees promised by that unsafe block.

When to Use This

You should only implement custom marker traits when you have introduced a mechanism (like a mutex, a memory barrier, or a specific ownership protocol) that the compiler cannot trace through raw pointers or opaque hardware interfaces. If the compiler already knows your type is Send or Sync, do not override it; let the auto-derivation do its job. When you do use it, document exactly why the invariants hold to ensure future maintenance doesn't break your concurrency assumptions.

Key Takeaway: Implementing custom marker traits is a manual promise to the compiler that your type handles cross-thread synchronization correctly, requiring an unsafe block to signal your intent to override default safety checks.