Advanced Level
Unsafe Rust
Chapter 19: Unsafe Rust ā ļø
Bypass Rust's safety guarantees when necessary with unsafe code.
Understanding Unsafe Rust
Unsafe Rust allows you to:
- Dereference raw pointers
- Call unsafe functions or methods
- Access or modify mutable static variables
- Implement unsafe traits
- Access fields of unions
Raw Pointers
fn main() {
let mut num = 5;
// Creating raw pointers
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
// Dereferencing raw pointers (unsafe)
unsafe {
println!("r1 is: {}", *r1);
println!("r2 is: {}", *r2);
}
}Dereferencing Null Pointers
fn main() {
let address = 0x012345usize;
let r = address as *const i32;
// This is unsafe and could cause segmentation fault
unsafe {
// println!("Value at address: {}", *r); // Don't do this!
}
}Unsafe Functions
Calling Unsafe Functions
unsafe fn dangerous() {
// Implementation
}
fn main() {
unsafe {
dangerous();
}
}Creating Safe Abstractions
use std::slice;
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = slice.len();
let ptr = slice.as_mut_ptr();
assert!(mid <= len);
unsafe {
(
slice::from_raw_parts_mut(ptr, mid),
slice::from_raw_parts_mut(ptr.add(mid), len - mid),
)
}
}
fn main() {
let mut v = vec![1, 2, 3, 4, 5, 6];
let r = &mut v[..];
let (a, b) = split_at_mut(r, 3);
assert_eq!(a, &mut [1, 2, 3]);
assert_eq!(b, &mut [4, 5, 6]);
}External Code Integration
Calling C Functions
extern "C" {
fn abs(input: i32) -> i32;
}
fn main() {
unsafe {
println!("Absolute value of -3 according to C: {}", abs(-3));
}
}Creating C-Compatible Functions
#[no_mangle]
pub extern "C" fn call_from_c() {
println!("Just called a Rust function from C!");
}
// In Cargo.toml:
// [lib]
// crate-type = ["cdylib"]Mutable Static Variables
Accessing Static Variables
static mut COUNTER: u32 = 0;
fn add_to_counter(inc: u32) {
unsafe {
COUNTER += inc;
}
}
fn main() {
add_to_counter(3);
unsafe {
println!("COUNTER: {}", COUNTER);
}
}Safe Static Variables
static HELLO_WORLD: &str = "Hello, world!";
fn main() {
println!("HELLO_WORLD: {}", HELLO_WORLD);
}Unsafe Traits
Implementing Unsafe Traits
unsafe trait Foo {
// methods go here
}
unsafe impl Foo for i32 {
// implementation goes here
}Union Types
Working with Unions
union MyUnion {
i: u32,
f: f32,
}
fn main() {
let u = MyUnion { i: 42 };
unsafe {
println!("u.i: {}", u.i);
println!("u.f: {}", u.f); // Undefined behavior!
}
}Practical Examples
Example 1: Memory Management with Raw Pointers
use std::alloc::{alloc, dealloc, Layout};
struct MyBox<T> {
ptr: *mut T,
layout: Layout,
}
impl<T> MyBox<T> {
fn new(value: T) -> MyBox<T> {
let layout = Layout::new::<T>();
unsafe {
let ptr = alloc(layout) as *mut T;
*ptr = value;
MyBox { ptr, layout }
}
}
fn deref(&self) -> &T {
unsafe { &*self.ptr }
}
}
impl<T> Drop for MyBox<T> {
fn drop(&mut self) {
unsafe {
dealloc(self.ptr as *mut u8, self.layout);
}
}
}
fn main() {
let my_box = MyBox::new(42);
println!("Value: {}", my_box.deref());
// Memory is automatically deallocated when my_box goes out of scope
}Example 2: FFI with C Library
use std::ffi::CString;
use std::os::raw::c_char;
extern "C" {
fn strlen(s: *const c_char) -> usize;
}
fn main() {
let s = CString::new("Hello, world!").expect("CString::new failed");
unsafe {
let len = strlen(s.as_ptr());
println!("Length: {}", len);
}
}When to Use Unsafe Code
Valid Use Cases
Interfacing with C code:
extern "C" { fn printf(format: *const c_char, ...) -> i32; }Building safe abstractions:
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { // Implementation using unsafe code to create safe abstraction }Performance optimization:
unsafe { // Bypassing bounds checking in performance-critical code }Hardware interaction:
let port_address = 0x3f8 as *mut u8; unsafe { *port_address = 0x42; // Writing to hardware port }
Common Mistakes
ā Dereferencing Invalid Raw Pointers
fn main() {
let mut num = 5;
let r1 = &num as *const i32;
let r2 = r1.wrapping_offset(1); // Points to invalid memory
unsafe {
// println!("{}", *r2); // Undefined behavior!
}
}ā Proper Raw Pointer Usage
fn main() {
let mut arr = [1, 2, 3, 4, 5];
let ptr = arr.as_ptr();
unsafe {
for i in 0..arr.len() {
println!("Value at index {}: {}", i, *ptr.add(i));
}
}
}ā Incorrect FFI Usage
extern "C" {
fn malloc(size: usize) -> *mut u8;
fn free(ptr: *mut u8);
}
fn main() {
unsafe {
let ptr = malloc(10);
// free(ptr); // Forgot to free memory - memory leak!
}
}ā Proper FFI Memory Management
extern "C" {
fn malloc(size: usize) -> *mut u8;
fn free(ptr: *mut u8);
}
fn main() {
unsafe {
let ptr = malloc(10);
if !ptr.is_null() {
free(ptr); // Properly free memory
}
}
}Key Takeaways
- ā Unsafe code bypasses Rust's safety guarantees
- ā Raw pointers can be dereferenced only in unsafe blocks
- ā Unsafe functions must be called within unsafe blocks
- ā
Use
extern "C"to interface with C code - ā Mutable static variables require unsafe access
- ā
Unsafe traits must be implemented with
unsafe impl - ā Unions allow accessing different data types at the same memory location
- ā Always prefer safe code when possible
- ā Follow Rust naming conventions and idioms for unsafe code
Ready for Chapter 20? ā Advanced Types