Rust Unsafe Programming Best Practices¶
Objective: Master senior-level Rust unsafe programming patterns for production systems. When you need to interface with C code, when you want to optimize performance, when you need enterprise-grade unsafe patternsโthese best practices become your weapon of choice.
Core Principles¶
- Minimize Unsafe: Use unsafe only when necessary
- Safe Abstractions: Wrap unsafe code in safe interfaces
- Documentation: Document all unsafe invariants
- Testing: Thoroughly test unsafe code
- Audit: Regularly audit unsafe code
Unsafe Patterns¶
Basic Unsafe Operations¶
// rust/01-basic-unsafe.rs
/*
Basic unsafe patterns and best practices for Rust
*/
use std::ptr;
use std::mem;
use std::alloc::{alloc, dealloc, Layout};
/// Safe wrapper around unsafe memory operations.
pub struct SafeBuffer {
ptr: *mut u8,
len: usize,
capacity: usize,
}
impl SafeBuffer {
/// Creates a new buffer with the specified capacity.
pub fn new(capacity: usize) -> Self {
let layout = Layout::from_size_align(capacity, 1).unwrap();
let ptr = unsafe { alloc(layout) };
if ptr.is_null() {
panic!("Failed to allocate memory");
}
Self {
ptr,
len: 0,
capacity,
}
}
/// Pushes a byte to the buffer.
pub fn push(&mut self, byte: u8) -> Result<(), &'static str> {
if self.len >= self.capacity {
return Err("Buffer is full");
}
unsafe {
ptr::write(self.ptr.add(self.len), byte);
}
self.len += 1;
Ok(())
}
/// Gets a byte from the buffer.
pub fn get(&self, index: usize) -> Option<u8> {
if index >= self.len {
return None;
}
unsafe {
Some(ptr::read(self.ptr.add(index)))
}
}
/// Gets a slice of the buffer.
pub fn as_slice(&self) -> &[u8] {
unsafe {
std::slice::from_raw_parts(self.ptr, self.len)
}
}
/// Gets a mutable slice of the buffer.
pub fn as_mut_slice(&mut self) -> &mut [u8] {
unsafe {
std::slice::from_raw_parts_mut(self.ptr, self.len)
}
}
}
impl Drop for SafeBuffer {
fn drop(&mut self) {
if !self.ptr.is_null() {
let layout = Layout::from_size_align(self.capacity, 1).unwrap();
unsafe {
dealloc(self.ptr, layout);
}
}
}
}
/// Safe wrapper around raw pointers.
pub struct SafePointer<T> {
ptr: *mut T,
_phantom: std::marker::PhantomData<T>,
}
impl<T> SafePointer<T> {
/// Creates a new safe pointer from a raw pointer.
pub fn new(ptr: *mut T) -> Self {
Self {
ptr,
_phantom: std::marker::PhantomData,
}
}
/// Gets a reference to the value.
pub fn get(&self) -> Option<&T> {
if self.ptr.is_null() {
None
} else {
unsafe {
Some(&*self.ptr)
}
}
}
/// Gets a mutable reference to the value.
pub fn get_mut(&mut self) -> Option<&mut T> {
if self.ptr.is_null() {
None
} else {
unsafe {
Some(&mut *self.ptr)
}
}
}
/// Sets the value at the pointer.
pub fn set(&mut self, value: T) {
if !self.ptr.is_null() {
unsafe {
ptr::write(self.ptr, value);
}
}
}
}
/// Safe wrapper around raw arrays.
pub struct SafeArray<T> {
ptr: *mut T,
len: usize,
_phantom: std::marker::PhantomData<T>,
}
impl<T> SafeArray<T> {
/// Creates a new safe array.
pub fn new(len: usize) -> Self {
let layout = Layout::array::<T>(len).unwrap();
let ptr = unsafe { alloc(layout) as *mut T };
if ptr.is_null() {
panic!("Failed to allocate memory");
}
Self {
ptr,
len,
_phantom: std::marker::PhantomData,
}
}
/// Gets an element by index.
pub fn get(&self, index: usize) -> Option<&T> {
if index >= self.len {
None
} else {
unsafe {
Some(&*self.ptr.add(index))
}
}
}
/// Gets a mutable element by index.
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
if index >= self.len {
None
} else {
unsafe {
Some(&mut *self.ptr.add(index))
}
}
}
/// Sets an element by index.
pub fn set(&mut self, index: usize, value: T) -> Result<(), &'static str> {
if index >= self.len {
Err("Index out of bounds")
} else {
unsafe {
ptr::write(self.ptr.add(index), value);
}
Ok(())
}
}
}
impl<T> Drop for SafeArray<T> {
fn drop(&mut self) {
if !self.ptr.is_null() {
let layout = Layout::array::<T>(self.len).unwrap();
unsafe {
dealloc(self.ptr as *mut u8, layout);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_safe_buffer() {
let mut buffer = SafeBuffer::new(10);
buffer.push(1).unwrap();
buffer.push(2).unwrap();
buffer.push(3).unwrap();
assert_eq!(buffer.get(0), Some(1));
assert_eq!(buffer.get(1), Some(2));
assert_eq!(buffer.get(2), Some(3));
assert_eq!(buffer.get(3), None);
}
#[test]
fn test_safe_pointer() {
let mut value = 42;
let mut pointer = SafePointer::new(&mut value as *mut i32);
assert_eq!(pointer.get(), Some(&42));
pointer.set(100);
assert_eq!(pointer.get(), Some(&100));
}
#[test]
fn test_safe_array() {
let mut array = SafeArray::new(5);
array.set(0, 1).unwrap();
array.set(1, 2).unwrap();
array.set(2, 3).unwrap();
assert_eq!(array.get(0), Some(&1));
assert_eq!(array.get(1), Some(&2));
assert_eq!(array.get(2), Some(&3));
assert_eq!(array.get(5), None);
}
}
FFI Patterns¶
// rust/02-ffi-patterns.rs
/*
FFI patterns and best practices for Rust
*/
use std::ffi::{CString, CStr};
use std::os::raw::{c_char, c_int};
/// Safe wrapper around C string operations.
pub struct SafeCString {
ptr: *mut c_char,
}
impl SafeCString {
/// Creates a new safe C string from a Rust string.
pub fn new(s: &str) -> Result<Self, std::ffi::NulError> {
let c_string = CString::new(s)?;
let ptr = c_string.into_raw();
Ok(Self { ptr })
}
/// Gets the C string as a Rust string.
pub fn to_string(&self) -> Result<String, std::str::Utf8Error> {
unsafe {
let c_str = CStr::from_ptr(self.ptr);
Ok(c_str.to_str()?.to_string())
}
}
/// Gets the raw pointer.
pub fn as_ptr(&self) -> *const c_char {
self.ptr
}
}
impl Drop for SafeCString {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe {
let _ = CString::from_raw(self.ptr);
}
}
}
}
/// Safe wrapper around C function calls.
pub struct SafeCFunction;
impl SafeCFunction {
/// Calls a C function with error handling.
pub fn call_c_function(input: &str) -> Result<i32, String> {
let c_string = CString::new(input)
.map_err(|e| format!("Failed to create C string: {}", e))?;
let result = unsafe {
// Simulate C function call
c_function_wrapper(c_string.as_ptr())
};
if result < 0 {
Err("C function returned error".to_string())
} else {
Ok(result)
}
}
}
/// Safe wrapper around C struct operations.
pub struct SafeCStruct {
data: Vec<u8>,
}
impl SafeCStruct {
/// Creates a new safe C struct.
pub fn new(size: usize) -> Self {
Self {
data: vec![0; size],
}
}
/// Gets a field from the struct.
pub fn get_field(&self, offset: usize, size: usize) -> Option<&[u8]> {
if offset + size <= self.data.len() {
Some(&self.data[offset..offset + size])
} else {
None
}
}
/// Sets a field in the struct.
pub fn set_field(&mut self, offset: usize, data: &[u8]) -> Result<(), &'static str> {
if offset + data.len() > self.data.len() {
return Err("Field would exceed struct bounds");
}
self.data[offset..offset + data.len()].copy_from_slice(data);
Ok(())
}
/// Gets the raw pointer to the struct.
pub fn as_ptr(&self) -> *const u8 {
self.data.as_ptr()
}
}
// External C function declaration
extern "C" {
fn c_function_wrapper(input: *const c_char) -> c_int;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_safe_c_string() {
let c_string = SafeCString::new("Hello, World!").unwrap();
assert_eq!(c_string.to_string().unwrap(), "Hello, World!");
}
#[test]
fn test_safe_c_struct() {
let mut c_struct = SafeCStruct::new(100);
let data = b"Hello";
c_struct.set_field(0, data).unwrap();
let field = c_struct.get_field(0, 5).unwrap();
assert_eq!(field, data);
}
}
Memory Management¶
// rust/03-memory-management.rs
/*
Memory management patterns and best practices for Rust
*/
use std::alloc::{alloc, dealloc, Layout};
use std::ptr;
/// Safe wrapper around raw memory allocation.
pub struct SafeMemory {
ptr: *mut u8,
size: usize,
layout: Layout,
}
impl SafeMemory {
/// Allocates memory with the specified size and alignment.
pub fn new(size: usize, align: usize) -> Result<Self, &'static str> {
let layout = Layout::from_size_align(size, align)
.map_err(|_| "Invalid layout")?;
let ptr = unsafe { alloc(layout) };
if ptr.is_null() {
return Err("Failed to allocate memory");
}
Ok(Self { ptr, size, layout })
}
/// Writes data to the memory.
pub fn write(&mut self, offset: usize, data: &[u8]) -> Result<(), &'static str> {
if offset + data.len() > self.size {
return Err("Write would exceed memory bounds");
}
unsafe {
ptr::copy_nonoverlapping(data.as_ptr(), self.ptr.add(offset), data.len());
}
Ok(())
}
/// Reads data from the memory.
pub fn read(&self, offset: usize, len: usize) -> Result<Vec<u8>, &'static str> {
if offset + len > self.size {
return Err("Read would exceed memory bounds");
}
let mut result = vec![0; len];
unsafe {
ptr::copy_nonoverlapping(self.ptr.add(offset), result.as_mut_ptr(), len);
}
Ok(result)
}
/// Gets the raw pointer.
pub fn as_ptr(&self) -> *const u8 {
self.ptr
}
/// Gets the mutable raw pointer.
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.ptr
}
}
impl Drop for SafeMemory {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe {
dealloc(self.ptr, self.layout);
}
}
}
}
/// Safe wrapper around memory-mapped operations.
pub struct SafeMemoryMap {
ptr: *mut u8,
size: usize,
}
impl SafeMemoryMap {
/// Creates a new memory map.
pub fn new(size: usize) -> Result<Self, &'static str> {
let layout = Layout::from_size_align(size, 1)
.map_err(|_| "Invalid layout")?;
let ptr = unsafe { alloc(layout) };
if ptr.is_null() {
return Err("Failed to allocate memory");
}
Ok(Self { ptr, size })
}
/// Maps data to the memory.
pub fn map(&mut self, data: &[u8]) -> Result<(), &'static str> {
if data.len() > self.size {
return Err("Data too large for memory map");
}
unsafe {
ptr::copy_nonoverlapping(data.as_ptr(), self.ptr, data.len());
}
Ok(())
}
/// Unmaps data from the memory.
pub fn unmap(&mut self) {
unsafe {
ptr::write_bytes(self.ptr, 0, self.size);
}
}
/// Gets a slice of the mapped memory.
pub fn as_slice(&self) -> &[u8] {
unsafe {
std::slice::from_raw_parts(self.ptr, self.size)
}
}
}
impl Drop for SafeMemoryMap {
fn drop(&mut self) {
if !self.ptr.is_null() {
let layout = Layout::from_size_align(self.size, 1).unwrap();
unsafe {
dealloc(self.ptr, layout);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_safe_memory() {
let mut memory = SafeMemory::new(100, 1).unwrap();
let data = b"Hello, World!";
memory.write(0, data).unwrap();
let read_data = memory.read(0, data.len()).unwrap();
assert_eq!(read_data, data);
}
#[test]
fn test_safe_memory_map() {
let mut memory_map = SafeMemoryMap::new(100).unwrap();
let data = b"Hello, World!";
memory_map.map(data).unwrap();
let slice = memory_map.as_slice();
assert_eq!(&slice[..data.len()], data);
}
}
TL;DR Runbook¶
Quick Start¶
// 1. Basic unsafe operations
unsafe {
let ptr = std::ptr::null_mut::<i32>();
let value = std::ptr::read(ptr);
}
// 2. Safe wrappers
struct SafeWrapper {
ptr: *mut u8,
}
impl SafeWrapper {
fn new() -> Self {
let layout = std::alloc::Layout::new::<u8>();
let ptr = unsafe { std::alloc::alloc(layout) };
Self { ptr }
}
}
// 3. FFI patterns
extern "C" {
fn c_function(input: *const i8) -> i32;
}
// 4. Memory management
unsafe {
let layout = std::alloc::Layout::new::<i32>();
let ptr = std::alloc::alloc(layout);
std::alloc::dealloc(ptr, layout);
}
Essential Patterns¶
// Complete unsafe programming setup
pub fn setup_rust_unsafe() {
// 1. Unsafe operations
// 2. Safe wrappers
// 3. FFI patterns
// 4. Memory management
// 5. Raw pointers
// 6. Memory allocation
// 7. C interop
// 8. Performance optimization
println!("Rust unsafe programming setup complete!");
}
This guide provides the complete machinery for Rust unsafe programming. Each pattern includes implementation examples, safety strategies, and real-world usage patterns for enterprise unsafe code.