15-rustTermsLevel_13unsafe trait / unsafe impl

unsafe trait / unsafe impl

Level 13 — Unsafe Rust & FFI A trait declaration (unsafe trait) and corresponding implementation (unsafe impl) where implementing the trait requires upholding compiler-unverifiable safety invariants.


1. Prerequisites

  • unsafe Block — Understanding unsafe superpowers, safety contracts, and invariant responsibilities.
  • Trait — Understanding standard Rust trait definitions and impl Trait for Type blocks.
  • Send Trait

2. Term Category

Rust Trait Specifier (safety-invariant implementing marker trait): An unsafe trait is a trait whose declaration is prefixed with unsafe (pub unsafe trait MyTrait). Marking a trait as unsafe declares that implementers must satisfy specific safety invariants that the compiler cannot verify. Consequently, implementing an unsafe trait requires using the unsafe impl keyword syntax (unsafe impl MyTrait for MyType).


3. Explanation

(1) Design Motivation — "Why did we design this?"

In standard Rust traits (pub trait Display), anyone can implement the trait for any type (impl Display for MyType). The compiler checks that all required trait methods are provided and that their parameter/return types match. Safe traits assume that implementing a trait cannot corrupt memory or break language safety guarantees.

However, some traits represent fundamental safety assertions relied upon by generic code:

  • Send: Asserting that a type is safe to transfer ownership across thread boundaries.
  • Sync: Asserting that a type is safe to share references (&T) concurrently across threads.
  • GlobalAlloc: Asserting that a custom memory allocator correctly manages raw memory layout allocations without pointer corruption.

If these marker traits were standard safe traits:

  1. Any developer could write impl Send for Rc<T> or impl Sync for RefCell<T>.
  2. Generic multi-threaded code (like std::thread::spawn) relies on T: Send to guarantee that data races cannot occur.
  3. If an incorrect implementation of Send were allowed without unsafe impl, generic code would blindly trust the implementation, causing catastrophic data races, memory corruption, and Undefined Behavior (UB).

unsafe trait solves this by transferring the implementation guarantee to the developer:

  • Declaring pub unsafe trait tells developers: "Implementing this trait carries a solemn safety promise."
  • Requiring unsafe impl forces the implementer to explicitly acknowledge that they have verified the safety invariants before claiming the trait.

(2) Reality Metaphor

Imagine an Industrial Pilot Certification Board:

  • A Safe Trait (pub trait Driver) is like a standard driving license for a passenger car: anyone who passes a basic automated written test (compiler type-checking) gets the license, because driving a passenger car carries standard, contained risk.
  • An unsafe trait (pub unsafe trait CommercialAirlinePilot) is like a commercial jet pilot certification: the certification carries immense safety obligations (thread safety or memory allocation invariants).
    • The flight candidate must sign a formal legal affidavit (unsafe impl) certifying under penalty of law that they have completed 1,500 hours of certified flight training (manually verified thread-safety invariants).
    • Air traffic control (generic code like thread::spawn) can then safely hand over the controls of a 300-passenger jet (execute concurrent code) relying 100% on the signed affidavit.

(3) Code Examples

Short Snippet (Implementing Send and Sync via unsafe impl)

use std::marker::{Send, Sync};

/// A custom wrapper around a raw pointer.
/// Raw pointers (*mut u8) do NOT automatically implement `Send` or `Sync`.
pub struct RawBuffer {
    ptr: *mut u8,
    len: usize,
}

// SAFETY: `RawBuffer` owns its heap allocation uniquely and does not expose
// interior mutability, making it safe to transfer ownership between threads.
unsafe impl Send for RawBuffer {}

// SAFETY: Shared references `&RawBuffer` allow reading data concurrently,
// and `RawBuffer` contains no internal non-thread-safe mutation (like RefCell).
unsafe impl Sync for RawBuffer {}

fn main() {
    let buf = RawBuffer { ptr: std::ptr::null_mut(), len: 0 };

    // Because `RawBuffer` implements `Send`, it can cross thread boundaries:
    std::thread::spawn(move || {
        println!("Buffer safely transferred to thread, len: {}", buf.len);
    }).join().unwrap();
}

Fuller Example (Defining a Custom unsafe trait)

/// An unsafe trait asserting that a data type guarantees a non-zero byte representation.
///
/// # Safety
/// Implementers MUST guarantee that memory initialized for this type
/// contains no zero-bytes (`0x00`) anywhere in its memory layout.
pub unsafe trait NonZeroBytes {
    fn is_valid_memory(&self) -> bool;
}

// Implement `NonZeroBytes` for a custom non-zero struct
pub struct NonZeroId(u32);

impl NonZeroId {
    pub fn new(val: u32) -> Option<Self> {
        if val == 0 { None } else { Some(NonZeroId(val)) }
    }
}

// SAFETY: We guarantee via `NonZeroId::new` constructor checks that
// `NonZeroId` inner `u32` can never be 0.
unsafe impl NonZeroBytes for NonZeroId {
    fn is_valid_memory(&self) -> bool {
        self.0 != 0
    }
}

/// Generic function relying on `NonZeroBytes` safety assertion
pub fn process_nonzero<T: NonZeroBytes>(val: &T) {
    assert!(val.is_valid_memory(), "Violated NonZeroBytes safety invariant!");
    println!("Processing non-zero memory safe type...");
}

fn main() {
    if let Some(id) = NonZeroId::new(42) {
        process_nonzero(&id);
    }
}

4. Common Mistakes & Pitfalls

Mistake 1: Implementing an unsafe trait with Standard impl Syntax

The mistake: Writing impl Send for MyType without prefixing unsafe.

Why it's wrong: Rust requires unsafe impl when implementing any trait declared as unsafe trait. The compiler enforces this syntax to ensure developers explicitly acknowledge safety responsibilities.

Incorrect:

struct MyPointer(*mut i32);

// ❌ Compiler Error: implementing an `unsafe trait` requires `unsafe impl`
impl Send for MyPointer {} 

Fix:

struct MyPointer(*mut i32);

// Correct: explicit `unsafe impl` acknowledging safety invariants
unsafe impl Send for MyPointer {}

Mistake 2: Marking a Trait unsafe trait when Method Calls are Simply Fallible

The mistake: Marking a trait unsafe trait merely because its methods can return Err or panic.

Why it's wrong: unsafe trait is strictly for traits where an incorrect implementation causes Undefined Behavior (UB) in generic code that trusts the trait. If an incorrect implementation merely causes a panic or unexpected result without memory corruption, the trait should be a standard safe trait.

Incorrect:

// ❌ Incorrect: Failing to parse a file does NOT cause Undefined Behavior. Should be safe trait!
pub unsafe trait ConfigParser {
    fn parse_file(&self) -> Result<String, String>;
}

Fix:

// Correct: Use standard safe trait with Result return type
pub trait ConfigParser {
    fn parse_file(&self) -> Result<String, String>;
}

Mistake 3: Falsely Implementing Send or Sync for Types with Interior Mutability

The mistake: Writing unsafe impl Sync for MyWrapper around non-thread-safe types like Rc<T> or RefCell<T> without thread synchronization.

Why it's wrong: RefCell<T> and Rc<T> use non-atomic reference counts and unsynchronized interior mutability. Implementing Send or Sync for them allows concurrent threads to mutate reference counts simultaneously, causing data races, memory corruption, and crashes.

Incorrect:

use std::cell::RefCell;

struct SharedState(RefCell<i32>);

// ❌ UNDEFINED BEHAVIOR! `RefCell` is NOT thread-safe for concurrent &SharedState access!
unsafe impl Sync for SharedState {} 

Fix:

use std::sync::Mutex;

// Correct: Use thread-safe `Mutex` for concurrent interior mutability
struct SharedState(Mutex<i32>);

// `Mutex<T>` automatically implements `Send` and `Sync` when T: Send.
// No manual `unsafe impl` needed!

5. Practice Exercises

Exercise 1: Building a Thread-Safe Custom Buffer (unsafe impl Send and Sync)

Scenario: Problem Statement: In high-performance networking and low-level systems code, data buffers are often backed by heap-allocated raw memory pointers (*mut u8). In Rust, raw pointers automatically opt out of the auto-traits Send and Sync (!Send and !Sync) to prevent accidental thread-safety violations.

Requirements: Design a custom raw byte buffer struct RawPacketBuffer that manages a heap-allocated memory region using std::alloc::{alloc, dealloc, Layout}. Because raw pointers do not implement Send or Sync automatically, implement Send and Sync for RawPacketBuffer using unsafe impl with explicit // SAFETY: invariant comments.

Requirements:

  1. RawPacketBuffer::new(capacity: usize) -> Self allocates heap memory for raw bytes.
  2. write_byte(&mut self, byte: u8) -> Result<(), &'static str> appends bytes up to capacity.
  3. as_slice(&self) -> &[u8] returns a safe byte slice over the written bytes.
  4. Implement unsafe impl Send for RawPacketBuffer and unsafe impl Sync for RawPacketBuffer.
  5. Implement Drop to safely deallocate the allocated memory layout.
  6. Include a unit test test_raw_packet_buffer_threads() using std::thread and Arc with assertions (assert_eq!) proving cross-thread ownership transfer (Send) and concurrent access (Sync).
Answer

Implementation

use std::alloc::{alloc, dealloc, Layout};
use std::slice;
use std::sync::Arc;
use std::thread;

/// A custom byte buffer backed by raw heap memory.
pub struct RawPacketBuffer {
    ptr: *mut u8,
    capacity: usize,
    len: usize,
    layout: Layout,
}

impl RawPacketBuffer {
    /// Constructs a new raw packet buffer with given capacity.
    pub fn new(capacity: usize) -> Self {
        assert!(capacity > 0, "Capacity must be greater than zero");
        let layout = Layout::array::<u8>(capacity).expect("Invalid layout creation");
        let ptr = unsafe { alloc(layout) };
        if ptr.is_null() {
            std::alloc::handle_alloc_error(layout);
        }
        Self {
            ptr,
            capacity,
            len: 0,
            layout,
        }
    }

    /// Appends a single byte into the buffer.
    pub fn write_byte(&mut self, byte: u8) -> Result<(), &'static str> {
        if self.len >= self.capacity {
            return Err("Buffer capacity exceeded");
        }
        unsafe {
            self.ptr.add(self.len).write(byte);
        }
        self.len += 1;
        Ok(())
    }

    /// Returns a safe slice view of written bytes.
    pub fn as_slice(&self) -> &[u8] {
        if self.ptr.is_null() || self.len == 0 {
            &[]
        } else {
            // SAFETY: `self.ptr` points to valid initialized memory of `self.len` bytes.
            unsafe { slice::from_raw_parts(self.ptr, self.len) }
        }
    }

    pub fn len(&self) -> usize {
        self.len
    }

    pub fn capacity(&self) -> usize {
        self.capacity
    }
}

// SAFETY: `RawPacketBuffer` uniquely owns its allocated heap memory pointer.
// Transferring ownership between threads is safe because no other thread can hold
// references to the internal raw pointer during transfer.
unsafe impl Send for RawPacketBuffer {}

// SAFETY: Shared immutable access `&RawPacketBuffer` only permits read operations
// (`as_slice`, `len`, `capacity`). No interior mutability exists without unique (`&mut`) borrows.
unsafe impl Sync for RawPacketBuffer {}

impl Drop for RawPacketBuffer {
    fn drop(&mut self) {
        if !self.ptr.is_null() {
            unsafe {
                dealloc(self.ptr, self.layout);
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_raw_packet_buffer_threads() {
        let mut buffer = RawPacketBuffer::new(64);
        buffer.write_byte(0xDE).unwrap();
        buffer.write_byte(0xAD).unwrap();
        buffer.write_byte(0xBE).unwrap();
        buffer.write_byte(0xEF).unwrap();

        assert_eq!(buffer.as_slice(), &[0xDE, 0xAD, 0xBE, 0xEF]);

        // Test `Send`: Transfer ownership into a child thread
        let handle = thread::spawn(move || {
            assert_eq!(buffer.len(), 4);
            assert_eq!(buffer.as_slice(), &[0xDE, 0xAD, 0xBE, 0xEF]);
            buffer.write_byte(0x00).unwrap();
            buffer.len()
        });

        let final_len = handle.join().unwrap();
        assert_eq!(final_len, 5);

        // Test `Sync`: Sharing via Arc across threads
        let mut sync_buf = RawPacketBuffer::new(32);
        sync_buf.write_byte(42).unwrap();
        let shared_buf = Arc::new(sync_buf);

        let shared_clone = Arc::clone(&shared_buf);
        let sync_handle = thread::spawn(move || {
            assert_eq!(shared_clone.as_slice(), &[42]);
        });

        assert_eq!(shared_buf.as_slice(), &[42]);
        sync_handle.join().unwrap();
    }
}

Technical Explanation

  1. Raw Pointer Auto-Trait Opt-out: Rust automatically implements Send and Sync for types whose fields are all Send and Sync. Because *mut u8 is raw and untrusted, the compiler marks RawPacketBuffer as !Send and !Sync by default.
  2. unsafe impl Send Rationale: Send indicates that ownership can move across thread boundaries. RawPacketBuffer owns its memory layout exclusively and has no shared aliasing, making thread movement completely safe.
  3. unsafe impl Sync Rationale: Sync indicates that shared references &T can be sent across threads. Since immutable methods (as_slice, len) only read from memory and do not perform unsynchronized interior mutation, concurrent reads cannot cause data races.
  4. Resource Management: Implementing Drop ensures heap memory allocated via alloc is deallocated using matching Layout rules, preventing memory leaks.

Exercise 2: Defining a Custom unsafe trait for Hardware DMA Alignment (unsafe trait DmaBuffer)

Scenario: Problem Statement: In embedded hardware systems and kernel drivers, Direct Memory Access (DMA) controllers bypass the CPU to write directly into system RAM. Hardware DMA controllers require buffers to satisfy strict invariants:

  1. Memory must be aligned to specific byte boundaries (e.g., 64-byte cache line alignment).
  2. The memory layout must remain pinned and physically valid during transfers.

Requirements: Because the Rust compiler cannot inspect physical hardware alignment contracts at compile time, declare a custom pub unsafe trait DmaBuffer.

Requirements:

  1. Declare pub unsafe trait DmaBuffer with methods fn dma_ptr(&self) -> *const u8, fn dma_len(&self) -> usize, and fn alignment(&self) -> usize. Document the safety obligations required of implementers in # Safety doc comments.
  2. Implement an aligned memory container struct AlignedDmaBuffer<const ALIGN: usize> backed by heap allocation with matching alignment.
  3. Provide unsafe impl<const ALIGN: usize> DmaBuffer for AlignedDmaBuffer<ALIGN> with safety rationale comments.
  4. Implement a generic function pub fn execute_dma_transfer<T: DmaBuffer>(buffer: &T) -> Result<usize, &'static str> that validates alignment invariants before executing transfer.
  5. Write unit tests using assert_eq! and assert! to verify alignment properties and execution results.
Answer

Implementation

use std::alloc::{alloc_zeroed, dealloc, Layout};
use std::ptr::NonNull;

/// An unsafe trait for types representing Direct Memory Access (DMA) safe buffers.
///
/// # Safety
/// Implementers of this trait MUST guarantee:
/// 1. `dma_ptr()` returns a non-null, valid memory pointer aligned to at least `alignment()` bytes.
/// 2. Memory starting at `dma_ptr()` for `dma_len()` bytes is valid for read operations
///    and remains pinned in memory for the lifetime of `&self`.
pub unsafe trait DmaBuffer {
    fn dma_ptr(&self) -> *const u8;
    fn dma_len(&self) -> usize;
    fn alignment(&self) -> usize;
}

/// A heap-allocated memory buffer guaranteed to be aligned to `ALIGN` bytes.
pub struct AlignedDmaBuffer<const ALIGN: usize> {
    ptr: NonNull<u8>,
    len: usize,
    layout: Layout,
}

impl<const ALIGN: usize> AlignedDmaBuffer<ALIGN> {
    /// Allocates an aligned DMA buffer of size `len`.
    /// `ALIGN` must be a power of two.
    pub fn new(len: usize) -> Self {
        assert!(len > 0, "Buffer length must be greater than zero");
        assert!(ALIGN.is_power_of_two(), "Alignment must be a power of two");

        let layout = Layout::from_size_align(len, ALIGN).expect("Invalid layout requested");
        let raw_ptr = unsafe { alloc_zeroed(layout) };
        let ptr = NonNull::new(raw_ptr).expect("Memory allocation failed");

        Self { ptr, len, layout }
    }

    /// Mutably access internal buffer to populate payload.
    pub fn as_mut_slice(&mut self) -> &mut [u8] {
        unsafe { std::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) }
    }
}

// SAFETY: `AlignedDmaBuffer` guarantees that `ptr` is allocated with `ALIGN` alignment
// using standard heap layout rules, and remains pinned for the duration of the struct lifetime.
unsafe impl<const ALIGN: usize> DmaBuffer for AlignedDmaBuffer<ALIGN> {
    fn dma_ptr(&self) -> *const u8 {
        self.ptr.as_ptr()
    }

    fn dma_len(&self) -> usize {
        self.len
    }

    fn alignment(&self) -> usize {
        ALIGN
    }
}

impl<const ALIGN: usize> Drop for AlignedDmaBuffer<ALIGN> {
    fn drop(&mut self) {
        unsafe {
            dealloc(self.ptr.as_ptr(), self.layout);
        }
    }
}

/// Generic DMA driver function relying on `DmaBuffer` safety guarantees.
pub fn execute_dma_transfer<T: DmaBuffer>(buffer: &T) -> Result<usize, &'static str> {
    let ptr_val = buffer.dma_ptr() as usize;
    let required_align = buffer.alignment();

    // Verify pointer alignment invariant guaranteed by `unsafe impl DmaBuffer`
    if ptr_val % required_align != 0 {
        return Err("Hardware fault: Buffer pointer breaks alignment invariant!");
    }

    if buffer.dma_len() == 0 {
        return Err("Empty buffer transfer requested");
    }

    Ok(buffer.dma_len())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_dma_buffer_alignment_and_transfer() {
        const ALIGNMENT: usize = 64; // 64-byte cache line alignment
        let mut dma_buf = AlignedDmaBuffer::<ALIGNMENT>::new(1024);

        let slice = dma_buf.as_mut_slice();
        slice[0] = 0xAA;
        slice[1023] = 0xFF;

        assert_eq!(dma_buf.dma_len(), 1024);
        assert_eq!(dma_buf.alignment(), 64);

        // Verify alignment property: address modulo ALIGNMENT must be 0
        let raw_addr = dma_buf.dma_ptr() as usize;
        assert_eq!(raw_addr % ALIGNMENT, 0, "Buffer address must be 64-byte aligned");

        // Execute DMA transfer through generic trait bound function
        let result = execute_dma_transfer(&dma_buf);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), 1024);
    }
}

Technical Explanation

  1. Why unsafe trait is required: Standard trait declarations allow any type to implement them without compiler verification. If DmaBuffer were a safe trait, a buggy implementation could return an unaligned pointer or invalid address, leading to hardware crashes or physical memory corruption. Marking DmaBuffer as unsafe trait forces implementers to use unsafe impl and guarantee hardware invariants.
  2. Const Generics Alignment: AlignedDmaBuffer<const ALIGN: usize> uses Rust const generics and Layout::from_size_align to request custom hardware-aligned heap memory.
  3. Consuming Generic Trait Bounds: Functions like execute_dma_transfer<T: DmaBuffer> can safely assume that types satisfying DmaBuffer uphold alignment and memory pinning contracts without adding extra runtime overhead.

Exercise 3: Zero-Copy Serialization via Custom unsafe trait (unsafe trait SafeForeignPod)

Scenario: Problem Statement: In high-throughput IPC, socket messaging, and FFI bindings, Rust data types are often serialized zero-copy by casting references &T directly into byte slices &[u8].

Requirements: However, arbitrary type serialization is unsafe if the type contains:

  1. Indirection pointers (String, Vec, raw pointers) that become dangling across process boundaries.
  2. Padding bytes containing uninitialized memory, which triggers Undefined Behavior when read as bytes or leaks confidential stack data.
  3. Non-deterministic struct layout (repr(Rust)).

Design a custom marker trait pub unsafe trait SafeForeignPod: Copy {} (Plain Old Data safe for zero-copy serialization).

Requirements:

  1. Declare pub unsafe trait SafeForeignPod: Copy {} with detailed # Safety invariants.
  2. Implement unsafe impl SafeForeignPod for primitive numeric types (u8, u32, u64, f64).
  3. Define a C-compatible telemetry struct SensorFrame with #[repr(C)], implementing Copy, Clone, and unsafe impl SafeForeignPod for SensorFrame.
  4. Implement a safe generic function pub fn serialize_pod<T: SafeForeignPod>(val: &T) -> &[u8] that casts &T to a byte slice &[u8].
  5. Write unit tests using assert_eq! verifying byte slice length, endianness conversion, and round-trip deserialization.
Answer

Implementation

use std::mem;
use std::slice;

/// Marker trait for types safe for zero-copy FFI serialization and byte transmute.
///
/// # Safety
/// Implementers of `SafeForeignPod` MUST satisfy:
/// 1. The type has a defined ABI layout (`#[repr(C)]` or primitive).
/// 2. The type contains NO raw pointers, references, or heap allocations.
/// 3. The type contains NO uninitialized padding bytes (all byte representations are valid).
pub unsafe trait SafeForeignPod: Copy {}

// SAFETY: Primitive numeric types have fixed layouts and no padding bytes.
unsafe impl SafeForeignPod for u8 {}
unsafe impl SafeForeignPod for u32 {}
unsafe impl SafeForeignPod for u64 {}
unsafe impl SafeForeignPod for f64 {}

/// A C-compatible sensor telemetry frame for hardware serial output.
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SensorFrame {
    pub sensor_id: u32,
    pub temperature: f64,
    pub timestamp: u64,
}

// SAFETY: `SensorFrame` uses `#[repr(C)]` layout, consists entirely of `SafeForeignPod` primitives
// (`u32`, `f64`, `u64`), and has no reference or pointer fields.
unsafe impl SafeForeignPod for SensorFrame {}

/// Safely serializes any type satisfying `SafeForeignPod` into a byte slice.
pub fn serialize_pod<T: SafeForeignPod>(val: &T) -> &[u8] {
    let ptr = val as *const T as *const u8;
    let size = mem::size_of::<T>();
    // SAFETY: `T` implements `SafeForeignPod`, guaranteeing valid initialized byte representation
    // of length `size` with standard alignment.
    unsafe { slice::from_raw_parts(ptr, size) }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_primitive_pod_serialization() {
        let val: u32 = 0x12345678;
        let bytes = serialize_pod(&val);
        assert_eq!(bytes.len(), mem::size_of::<u32>());

        if cfg!(target_endian = "little") {
            assert_eq!(bytes, &[0x78, 0x56, 0x34, 0x12]);
        } else {
            assert_eq!(bytes, &[0x12, 0x34, 0x56, 0x78]);
        }
    }

    #[test]
    fn test_struct_pod_serialization() {
        let frame = SensorFrame {
            sensor_id: 101,
            temperature: 36.5,
            timestamp: 1600000000,
        };

        let serialized = serialize_pod(&frame);
        assert_eq!(serialized.len(), mem::size_of::<SensorFrame>());

        // Reconstruct reference safely from serialized byte slice
        let reconstructed_ptr = serialized.as_ptr() as *const SensorFrame;
        let reconstructed_frame = unsafe { *reconstructed_ptr };

        assert_eq!(frame, reconstructed_frame);
        assert_eq!(reconstructed_frame.sensor_id, 101);
        assert_eq!(reconstructed_frame.temperature, 36.5);
    }
}

Technical Explanation

  1. Safety Assertions as Trait Bounds: serialize_pod is a completely safe function (fn serialize_pod) because the safety burden was already verified at the unsafe impl SafeForeignPod implementation site.
  2. Memory Alignment & Layout (#[repr(C)]): Rust's default struct layout (repr(Rust)) allows field reordering. #[repr(C)] guarantees fixed C-compatible struct field order across compilation target boundaries.
  3. Preventing Undefined Behavior: Passing non-SafeForeignPod types (e.g. types with String or pointers) into low-level transmutes causes immediate UB. The custom unsafe trait creates a compile-time safety gate preventing unsafe transmutes.


7. Key Takeaways

  • An unsafe trait is a trait whose declaration is prefixed with unsafe trait, signaling that implementation carries safety obligations.
  • Implementing an unsafe trait requires explicit unsafe impl syntax.
  • Send and Sync are the most famous standard library unsafe trait examples.
  • Use unsafe trait only when an incorrect implementation allows generic code to cause Undefined Behavior (UB).
Built with LogoFlowershow