liveBook · Manning

Unsafe block

As explained in Rust in Action: Systems programming concepts and techniques

An unsafe block is a Rust code region in which operations requiring explicit programmer responsibility for safety are permitted.

An `unsafe { ... }` block permits operations Rust cannot verify as memory-safe on its own. It does not switch off the borrow checker or make the code safe automatically: the programmer takes responsibility for the operation’s safety. Use it only when needed, and investigate alternatives before using `std::mem::transmute`, which discards type safety. section 5.1

Three situations show why the boundary matters:

  • Reinterpreting bits: `unsafe { std::mem::transmute(a) }` treats an `f32` value as a `u32` without changing its underlying bits. section 5.1
  • Reading through a raw pointer: `unsafe { *ptr }` permits the dereference, but a pointer to address zero still crashes the memory-scanning program. Starting at address one does not fix it: the scan can still hit memory the process cannot access. section 6.4.2
  • Accessing a `static mut`: both reading and writing a mutable global require an unsafe block. A signal handler can use such a global as a flag for the main loop, but avoid global state where possible. section 12.6.1 section 12.12

A small boundary around a pointer read

In a byte-by-byte memory scan, the pointer can be created with `let ptr = i as *const u8;`, while the read is written `let byte_at_addr = unsafe { *ptr };`. The block marks the operation needing programmer scrutiny; it does not establish that `i` names readable memory. section 6.4.2

Listing 5.2 Interpreting a float’s bit string as an integer
 fn main() {
   let a: f32 = 42.42;
   let frankentype: u32 = unsafe {
     std::mem::transmute(a)             #1
   };
 
   println!("{}", frankentype);         #2
   println!("{:032b}", frankentype);    #3
 
   let b: f32 = unsafe {
     std::mem::transmute(frankentype)
   };
   println!("{}", b);
   assert_eq!(a, b);                    #4
 }

Follow the `f32`-to-`u32` conversion and back to see where the bit reinterpretations need unsafe blocks. section 5.1

Listing 6.12 Attempting to scan a running program’s memory byte by byte
 1 fn main() {
 2     let mut n_nonzero = 0;
 3  
 4     for i in 0..10000 {
 5         let ptr = i as *const u8;              #1
 6         let byte_at_addr = unsafe { *ptr };    #2
 7  
 8         if byte_at_addr != 0 {
 9             n_nonzero += 1;
10         }
11     }
12  
13     println!("non-zero bytes in memory: {}", n_nonzero);
14 }

Notice that an unsafe pointer read can compile yet crash when the scan starts at address zero. section 6.4.2

How Rust in Action: Systems programming concepts and techniques covers it

How deep it goes
Defines the safety responsibility and works through transmute and raw-pointer dereferencing, but does not provide a comprehensive unsafe-operation reference.
Examples
Shows an `f32`–`u32` transmute round trip and a byte-by-byte memory scan that crashes at address zero.
What it assumes
Assumes basic Rust types and introduces raw-pointer creation and dereferencing through a memory-scanning program.
What you can do after
Practice/perform contained unsafe operations while recognizing that permission to dereference a pointer does not establish a readable address.
How it connects
Connects raw pointers and segmentation faults to memory access, and type reinterpretation to bit patterns.

Also explained in

Compare how 3 books explain unsafe block →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Rust in Action: Systems programming concepts and techniques. The section links point to the parts of the book it draws on.