unsafe trait
Functional Programming
Tutorial
The Problem
This example covers a specific aspect of Rust's unsafe programming model: raw memory manipulation, FFI interop, allocator customization, or soundness principles. These topics are essential for systems programming — writing OS components, device drivers, game engines, and any code that must interact with C libraries or control memory layout precisely. Rust's unsafe system is designed to confine unsafety to small, auditable regions while maintaining safety in the surrounding code.
🎯 Learning Outcomes
Code Example
#![allow(clippy::all)]
//! 702 — Unsafe Traits
//! unsafe trait, unsafe impl, and Send/Sync marker traits.
use std::sync::{Arc, Mutex};
use std::thread;
// ── Custom unsafe trait ───────────────────────────────────────────────────
/// Marker trait: implementors guarantee this type can cross thread boundaries.
///
/// # Safety
/// The type must not contain non-thread-safe interior mutability or raw
/// pointers that could alias across threads.
pub unsafe trait ThreadSafe: Send + Sync {
fn describe(&self) -> String;
}
// ── A type safe to implement ThreadSafe on ────────────────────────────────
pub struct AtomicCounter {
value: std::sync::atomic::AtomicI64,
}
impl AtomicCounter {
pub fn new(v: i64) -> Self {
Self {
value: std::sync::atomic::AtomicI64::new(v),
}
}
pub fn get(&self) -> i64 {
self.value.load(std::sync::atomic::Ordering::SeqCst)
}
pub fn increment(&self) {
self.value.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
}
}
// SAFETY: AtomicCounter uses AtomicI64 — all operations are thread-safe.
// No raw pointers, no non-Sync interior mutability.
unsafe impl ThreadSafe for AtomicCounter {
fn describe(&self) -> String {
format!("AtomicCounter({})", self.get())
}
}
// ── Type that is NOT Send (contains *mut T) ───────────────────────────────
pub struct NotSend {
_ptr: *mut i32, // *mut T is !Send by default
}
// fn use_not_send_in_thread(x: NotSend) {
// thread::spawn(move || drop(x)); // compile error: *mut i32 is not Send
// }
// ── Demonstrate Send/Sync in practice ────────────────────────────────────
fn run_in_thread<T: ThreadSafe + 'static>(val: Arc<T>) {
let v = Arc::clone(&val);
thread::spawn(move || println!("Thread: {}", v.describe()))
.join()
.unwrap();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_atomic_counter_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<AtomicCounter>(); // won't compile if wrong
}
#[test]
fn test_threadsafe_describe() {
let c = AtomicCounter::new(42);
assert_eq!(c.describe(), "AtomicCounter(42)");
}
#[test]
fn test_concurrent_increment() {
let c = Arc::new(AtomicCounter::new(0));
let handles: Vec<_> = (0..10)
.map(|_| {
let cc = Arc::clone(&c);
thread::spawn(move || cc.increment())
})
.collect();
for h in handles {
h.join().unwrap();
}
assert_eq!(c.get(), 10);
}
}Key Differences
unsafe for these operations; OCaml achieves safety through the GC and type system without explicit unsafe regions.extern "C"; OCaml uses ctypes which wraps C types in OCaml values.#[repr(C)], custom allocators); OCaml's GC manages memory layout automatically.OCaml Approach
OCaml's GC and type system eliminate most of the need for these unsafe operations. The equivalent functionality typically uses:
ctypes library for external function callsBigarray for controlled raw memory access Bytes.t for mutable byte sequencesOCaml programs rarely need operations equivalent to these Rust unsafe patterns.
Full Source
#![allow(clippy::all)]
//! 702 — Unsafe Traits
//! unsafe trait, unsafe impl, and Send/Sync marker traits.
use std::sync::{Arc, Mutex};
use std::thread;
// ── Custom unsafe trait ───────────────────────────────────────────────────
/// Marker trait: implementors guarantee this type can cross thread boundaries.
///
/// # Safety
/// The type must not contain non-thread-safe interior mutability or raw
/// pointers that could alias across threads.
pub unsafe trait ThreadSafe: Send + Sync {
fn describe(&self) -> String;
}
// ── A type safe to implement ThreadSafe on ────────────────────────────────
pub struct AtomicCounter {
value: std::sync::atomic::AtomicI64,
}
impl AtomicCounter {
pub fn new(v: i64) -> Self {
Self {
value: std::sync::atomic::AtomicI64::new(v),
}
}
pub fn get(&self) -> i64 {
self.value.load(std::sync::atomic::Ordering::SeqCst)
}
pub fn increment(&self) {
self.value.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
}
}
// SAFETY: AtomicCounter uses AtomicI64 — all operations are thread-safe.
// No raw pointers, no non-Sync interior mutability.
unsafe impl ThreadSafe for AtomicCounter {
fn describe(&self) -> String {
format!("AtomicCounter({})", self.get())
}
}
// ── Type that is NOT Send (contains *mut T) ───────────────────────────────
pub struct NotSend {
_ptr: *mut i32, // *mut T is !Send by default
}
// fn use_not_send_in_thread(x: NotSend) {
// thread::spawn(move || drop(x)); // compile error: *mut i32 is not Send
// }
// ── Demonstrate Send/Sync in practice ────────────────────────────────────
fn run_in_thread<T: ThreadSafe + 'static>(val: Arc<T>) {
let v = Arc::clone(&val);
thread::spawn(move || println!("Thread: {}", v.describe()))
.join()
.unwrap();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_atomic_counter_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<AtomicCounter>(); // won't compile if wrong
}
#[test]
fn test_threadsafe_describe() {
let c = AtomicCounter::new(42);
assert_eq!(c.describe(), "AtomicCounter(42)");
}
#[test]
fn test_concurrent_increment() {
let c = Arc::new(AtomicCounter::new(0));
let handles: Vec<_> = (0..10)
.map(|_| {
let cc = Arc::clone(&c);
thread::spawn(move || cc.increment())
})
.collect();
for h in handles {
h.join().unwrap();
}
assert_eq!(c.get(), 10);
}
}
✓ Tests
Rust test suite
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_atomic_counter_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<AtomicCounter>(); // won't compile if wrong
}
#[test]
fn test_threadsafe_describe() {
let c = AtomicCounter::new(42);
assert_eq!(c.describe(), "AtomicCounter(42)");
}
#[test]
fn test_concurrent_increment() {
let c = Arc::new(AtomicCounter::new(0));
let handles: Vec<_> = (0..10)
.map(|_| {
let cc = Arc::clone(&c);
thread::spawn(move || cc.increment())
})
.collect();
for h in handles {
h.join().unwrap();
}
assert_eq!(c.get(), 10);
}
}
Exercises
bytemuck for transmute, CString for FFI strings) and implement it.