Loading page…
Rust walkthroughs
Loading page…
NonZero types (like NonZeroU32, NonZeroIsize, etc.) wrap integer types with a compile-time guarantee that the value is never zero. This enables powerful optimizations, particularly that Option<NonZeroT> has the same size as the underlying integer type.
Key characteristics:
Option<NonZeroT> is same size as TNonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize (and signed variants)new() returning OptionWhen to use NonZero types:
use std::num::NonZeroU32;
fn main() {
// Create a NonZeroU32
let nonzero = NonZeroU32::new(42).expect("42 is non-zero");
println!("Value: {}", nonzero);
// Zero returns None
let zero = NonZeroU32::new(0);
assert!(zero.is_none());
// Get the underlying value
let value: u32 = nonzero.get();
println!("Underlying value: {}", value);
}use std::num::NonZeroU32;
fn main() {
// Option<NonZeroU32> is the same size as u32!
// None is represented as 0
println!("Size of Option<NonZeroU32>: {}", std::mem::size_of::<Option<NonZeroU32>>());
println!("Size of u32: {}", std::mem::size_of::<u32>());
// Both are 4 bytes!
println!("Size of Option<u32>: {}", std::mem::size_of::<Option<u32>>());
// This is 8 bytes (4 for value + 1 for discriminant + padding)
// Create optional non-zero values
let some = NonZeroU32::new(42);
let none: Option<NonZeroU32> = None;
println!("Some: {:?}", some);
println!("None: {:?}", none);
}use std::num::NonZeroU64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct UserId(NonZeroU64);
impl UserId {
fn new(id: u64) -> Option<Self> {
NonZeroU64::new(id).map(Self)
}
fn get(&self) -> u64 {
self.0.get()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct ProductId(NonZeroU32);
impl ProductId {
fn new(id: u32) -> Option<Self> {
NonZeroU32::new(id).map(Self)
}
fn get(&self) -> u32 {
self.0.get()
}
}
fn main() {
let user = UserId::new(12345).expect("Valid user ID");
let product = ProductId::new(999).expect("Valid product ID");
println!("User ID: {}", user.get());
println!("Product ID: {}", product.get());
// Zero IDs are rejected
assert!(UserId::new(0).is_none());
assert!(ProductId::new(0).is_none());
// Optional IDs are memory-efficient
let maybe_user: Option<UserId> = Some(user);
println!("Size of Option<UserId>: {}", std::mem::size_of::<Option<UserId>>());
// Same as u64!
}use std::num::NonZeroU32;
fn safe_divide(numerator: u32, denominator: NonZeroU32) -> u32 {
// No need to check for zero - it's guaranteed non-zero!
numerator / denominator.get()
}
fn main() {
let divisor = NonZeroU32::new(10).unwrap();
let result = safe_divide(100, divisor);
println!("100 / 10 = {}", result);
// Division by zero is impossible with NonZero
// safe_divide(100, NonZeroU32::new(0).unwrap()); // Would panic at unwrap
}use std::num::NonZeroUsize;
struct SparseArray<T> {
data: Vec<Option<T>>,
}
impl<T> SparseArray<T> {
fn new(size: usize) -> Self {
Self {
data: vec![None; size],
}
}
fn set(&mut self, index: usize, value: T) {
self.data[index] = Some(value);
}
fn get(&self, index: usize) -> Option<&T> {
self.data.get(index)?.as_ref()
}
}
// Compact index storage using NonZero
struct CompactIndex {
// Uses Option<NonZeroUsize> for optional indices
// None means "no index", Some(NonZeroUsize(n-1)) means index n
// This is same size as usize!
index: Option<NonZeroUsize>,
}
impl CompactIndex {
fn none() -> Self {
Self { index: None }
}
fn some(index: usize) -> Self {
// Store index + 1 so 0 can be represented
Self {
index: NonZeroUsize::new(index.checked_add(1).expect("Index overflow")),
}
}
fn get(&self) -> Option<usize> {
self.index.map(|n| n.get() - 1)
}
}
fn main() {
let mut arr = SparseArray::new(10);
arr.set(3, String::from("Hello"));
arr.set(7, String::from("World"));
println!("Index 3: {:?}", arr.get(3));
println!("Index 5: {:?}", arr.get(5));
// Compact index storage
let mut indices = Vec::new();
for i in 0..10 {
indices.push(CompactIndex::some(i * 2));
}
indices.push(CompactIndex::none());
println!("Index storage: {} bytes for {} entries + 1 None",
indices.len() * std::mem::size_of::<CompactIndex>(),
indices.len());
}use std::num::*;
fn main() {
// Unsigned types
let u8_ = NonZeroU8::new(1).unwrap();
let u16_ = NonZeroU16::new(1).unwrap();
let u32_ = NonZeroU32::new(1).unwrap();
let u64_ = NonZeroU64::new(1).unwrap();
let u128_ = NonZeroU128::new(1).unwrap();
let usize_ = NonZeroUsize::new(1).unwrap();
// Signed types (since Rust 1.34)
let i8_ = NonZeroI8::new(1).unwrap();
let i16_ = NonZeroI16::new(1).unwrap();
let i32_ = NonZeroI32::new(1).unwrap();
let i64_ = NonZeroI64::new(1).unwrap();
let i128_ = NonZeroI128::new(1).unwrap();
let isize_ = NonZeroIsize::new(1).unwrap();
println!("All NonZero types created successfully");
// Signed NonZero can also be negative
let negative = NonZeroI32::new(-42).unwrap();
println!("Negative value: {}", negative);
}use std::num::NonZeroU32;
fn main() {
let a = NonZeroU32::new(10).unwrap();
let b = NonZeroU32::new(5).unwrap();
// NonZero supports arithmetic operations (since Rust 1.51)
// Multiplication of two NonZero values is NonZero
let product = a.checked_mul(b);
println!("10 * 5 = {:?}", product);
// Division of NonZero by NonZero is NonZero
let quotient = a.checked_div(b);
println!("10 / 5 = {:?}", quotient);
// Addition and subtraction can result in zero, so return Option
let sum = a.get().saturating_add(b.get());
println!("10 + 5 = {}", sum);
// power of NonZero is NonZero
let squared = a.checked_pow(2);
println!("10^2 = {:?}", squared);
}use std::num::NonZeroU32;
use std::os::raw::c_uint;
// C function that expects non-zero handle
// void process_handle(unsigned int handle); // handle must not be 0
fn process_handle(handle: NonZeroU32) {
// Safe: handle is guaranteed non-zero
let raw: c_uint = handle.get();
println!("Processing handle: {}", raw);
// unsafe { process_handle_ffi(raw); }
}
// C function that returns non-zero handle or 0 on error
fn get_handle() -> Option<NonZeroU32> {
// Simulate FFI call
let raw: c_uint = 42;
NonZeroU32::new(raw)
}
fn main() {
// Safe FFI with compile-time guarantees
if let Some(handle) = get_handle() {
process_handle(handle);
} else {
println!("Failed to get handle");
}
}use std::num::{NonZeroU32, NonZeroU64};
// Before: Uses extra byte for Option discriminant
struct UserV1 {
id: Option<u64>, // 16 bytes (8 + discriminant + padding)
age: Option<u32>, // 8 bytes (4 + discriminant + padding)
}
// After: Uses NonZero for zero-cost Option
struct UserV2 {
id: Option<NonZeroU64>, // 8 bytes
age: Option<NonZeroU32>, // 4 bytes
}
impl UserV2 {
fn new(id: u64, age: u32) -> Option<Self> {
Some(Self {
id: NonZeroU64::new(id)?,
age: NonZeroU32::new(age)?,
})
}
fn id(&self) -> u64 {
self.id.map(|n| n.get()).unwrap_or(0)
}
fn age(&self) -> u32 {
self.age.map(|n| n.get()).unwrap_or(0)
}
}
fn main() {
println!("Size of UserV1: {}", std::mem::size_of::<UserV1>());
println!("Size of UserV2: {}", std::mem::size_of::<UserV2>());
// UserV2 is significantly smaller!
let user = UserV2::new(12345, 25).expect("Valid user");
println!("User ID: {}, Age: {}", user.id(), user.age());
}use std::num::NonZeroUsize;
use std::ptr::NonNull;
// Arena allocator index
struct Arena<T> {
data: Vec<T>,
}
impl<T> Arena<T> {
fn new() -> Self {
Self { data: Vec::new() }
}
fn alloc(&mut self, value: T) -> Index {
let index = self.data.len();
self.data.push(value);
// +1 so 0 can mean "null"
Index(NonZeroUsize::new(index + 1).unwrap())
}
fn get(&self, index: Index) -> &T {
&self.data[index.0.get() - 1]
}
}
#[derive(Clone, Copy)]
struct Index(NonZeroUsize);
impl Index {
fn null() -> Option<Self> {
None
}
}
struct Node<T> {
value: T,
next: Option<Index>, // Same size as usize!
}
fn main() {
let mut arena: Arena<String> = Arena::new();
let a = arena.alloc(String::from("First"));
let b = arena.alloc(String::from("Second"));
let c = arena.alloc(String::from("Third"));
println!("Item at a: {}", arena.get(a));
println!("Item at b: {}", arena.get(b));
println!("Size of Option<Index>: {}", std::mem::size_of::<Option<Index>>());
}use std::num::NonZeroU32;
fn main() {
// Safe construction
let safe = NonZeroU32::new(42).expect("Non-zero");
// Unsafe construction when you KNOW the value is non-zero
// Only use this when the invariant is guaranteed elsewhere
let unsafe_val: u32 = 100;
let unsafe_nonzero: NonZeroU32 = unsafe {
NonZeroU32::new_unchecked(unsafe_val)
};
println!("Safe: {}, Unsafe: {}", safe, unsafe_nonzero);
// NEVER do this:
// let bad = unsafe { NonZeroU32::new_unchecked(0) }; // UB!
}use std::num::{NonZeroU32, NonZeroU64};
fn main() {
let u32_val = NonZeroU32::new(42).unwrap();
// Convert to u64
let u64_val = NonZeroU64::from(u32_val);
println!("u64: {}", u64_val);
// Get underlying value and convert
let raw: u32 = u32_val.get();
let as_u64: u64 = raw as u64;
// Or use From/Into
let converted: u64 = u32_val.get().into();
println!("Converted: {}", converted);
}use std::num::NonZeroUsize;
fn main() {
let len = 10;
// Safe indexing with NonZero
fn safe_index(slice: &[i32], index: NonZeroUsize) -> Option<&i32> {
// NonZero is 1-indexed conceptually, so subtract 1
slice.get(index.get() - 1)
}
let data = [1, 2, 3, 4, 5];
let idx = NonZeroUsize::new(3).unwrap();
if let Some(&val) = safe_index(&data, idx) {
println!("Value at index 3 (1-indexed): {}", val);
}
// Out of bounds
let big_idx = NonZeroUsize::new(100).unwrap();
assert!(safe_index(&data, big_idx).is_none());
}use std::num::NonZeroU32;
// Constants can be created using const fn
const MAX_ITEMS: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1000) };
const PAGE_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(4096) };
fn main() {
println!("Max items: {}", MAX_ITEMS);
println!("Page size: {}", PAGE_SIZE);
// Use in const context
const BUFFER_SIZE: usize = PAGE_SIZE.get() * 2;
println!("Buffer size: {}", BUFFER_SIZE);
}NonZero Types:
| Type | Underlying | Min Value | Max Value |
|------|------------|-----------|----------|
| NonZeroU8 | u8 | 1 | 255 |
| NonZeroU16 | u16 | 1 | 65535 |
| NonZeroU32 | u32 | 1 | 4294967295 |
| NonZeroU64 | u64 | 1 | 2^64-1 |
| NonZeroU128 | u128 | 1 | 2^128-1 |
| NonZeroUsize | usize | 1 | platform max |
| NonZeroI8 | i8 | -128..=-1, 1..=127 |
| NonZeroI32 | i32 | excludes 0 |
Key Methods:
| Method | Description | Safety |
|--------|-------------|--------|
| new(n) | Create from value (returns Option) | Safe |
| new_unchecked(n) | Create without checking | Unsafe |
| get() | Get underlying value | Safe |
| checked_mul() | Multiply NonZero values | Safe |
| checked_div() | Divide NonZero values | Safe |
| checked_pow() | Raise to power | Safe |
Size Comparison:
| Type | Size |
|------|------|
| u32 | 4 bytes |
| NonZeroU32 | 4 bytes |
| Option<u32> | 8 bytes |
| Option<NonZeroU32> | 4 bytes |
When to Use NonZero:
| Scenario | Appropriate? | |----------|-------------| | IDs that can't be zero | ✅ Yes | | Optional integers with memory constraints | ✅ Yes | | Division denominators | ✅ Yes | | FFI non-zero parameters | ✅ Yes | | General integer arithmetic | ❌ Use normal types | | Zero is a valid value | ❌ Use normal types |
Key Points:
Option<NonZeroT> has the same size as T (null optimization)new() returns Optionnew_unchecked() is unsafe and requires the invariant