---
title: How to Use std::num for Number-Related Traits (NonZero, Wrapping, etc.)
summary: Import types from std::num to enforce non-zero values or enable wrapping arithmetic safely.
lang: en
slug: how-to-use-stdnum-for-number-related-traits-nonzero-wrapping-etc
published: 2026-04-17T12:05:42+00:00
updated: 2026-04-25T22:00:13+00:00
canonical: https://www.rustfaq.org/en/how-to-use-stdnum-for-number-related-traits-nonzero-wrapping-etc/
---

# How to Use std::num for Number-Related Traits (NonZero, Wrapping, etc.)



## When zero breaks your logic

You are building a cache. The key is a `u32`. You need a special value to mean "no key yet." You pick `0`. Later, you realize `0` is a valid key for the first item. Now you are juggling `Option<u32>` or inventing a sentinel like `u32::MAX`. Both feel messy. `Option` wastes space. Sentinels create magic numbers that confuse readers.

Rust has a better way. The `std::num` module provides wrapper types that bake constraints directly into the type system. `NonZeroU32` guarantees the value is never zero. `Wrapping<u8>` guarantees arithmetic wraps around instead of panicking. `Saturating<i32>` guarantees values clamp to limits. These types turn runtime bugs into compile-time errors.

## Wrapper types that enforce rules

Standard integers like `u32` or `i32` are flexible. They hold any value in their range. That flexibility is dangerous when your domain has stricter rules. A user ID should never be zero. A volume slider should never exceed 100. A checksum calculation should wrap on overflow.

`std::num` types enforce these rules at the type level. The compiler checks the rules for you. If you try to create a `NonZeroU32` with zero, the code does not compile. If you try to add two `Wrapping` values, the result wraps silently. The type tells the reader exactly what to expect.

Think of these types as smart containers. A `u32` is a box that holds any number. `NonZeroU32` is a box with a lock that only accepts non-zero keys. If you try to put zero in, the lock jams. The compiler is the mechanic who refuses to hand you the key until you fix the input.

## NonZero: The zero-proof integer

`NonZero` types exist for every integer size: `NonZeroU8`, `NonZeroU16`, `NonZeroU32`, `NonZeroU64`, `NonZeroU128`, and the signed variants. They work the same way. The constructor returns an `Option`. A valid value returns `Some`. Zero returns `None`.

```rust
use std::num::NonZeroU32;

fn main() {
    // NonZeroU32::new returns Option<NonZeroU32>.
    // The compiler forces you to handle the case where the value is zero.
    let id = NonZeroU32::new(42).expect("ID cannot be zero");

    // This would fail to compile if you passed 0.
    // let bad_id = NonZeroU32::new(0).expect("...");

    println!("Valid ID: {}", id);
}
```

The `expect` call panics if the value is zero. In production code, you usually return an error or use `if let` to handle the failure gracefully. The point is the compiler makes you think about zero. You cannot accidentally create a zero value.

**Convention aside:** `NonZero` types are the secret weapon for `HashMap` performance. When you use `NonZero` as a key, the hash map can use a more compact internal representation. The map uses zero as a marker for empty slots. Since the key can never be zero, the map packs data tighter and skips extra checks. This reduces memory usage and speeds up lookups. If you are storing integers in a hash map and zero is invalid, switch to `NonZero`. The performance gain is real.

## Wrapping and Saturating: Choosing overflow behavior

Rust panics on integer overflow in debug builds. This is safe but sometimes wrong. A circular buffer index should wrap. A volume counter should clamp. `std::num` gives you types that define the overflow behavior upfront.

`Wrapping<T>` implements arithmetic traits so that overflow wraps around. `255u8 + 1` becomes `0`. No panic. No silent truncation in release mode. The behavior is consistent across all build profiles.

`Saturating<T>` implements arithmetic so that overflow clamps to the maximum or minimum value. `255u8 + 1` stays `255`. `-128i8 - 1` stays `-128`. This is useful for UI counters, progress bars, and any logic where hitting a limit should stop the value from jumping to the other side.

```rust
use std::num::{Wrapping, Saturating};

fn main() {
    // Wrapping arithmetic cycles like a clock.
    // 255 + 1 wraps to 0.
    let wrapped = Wrapping(255u8) + Wrapping(1u8);

    // Saturating arithmetic clamps at the limit.
    // 255 + 1 stays 255.
    let saturated = Saturating(255u8) + Saturating(1u8);

    println!("Wrapped: {}, Saturated: {}", wrapped.0, saturated.0);
}
```

**Convention aside:** Use `Wrapping` for low-level bit manipulation, checksums, and circular buffers. Use `Saturating` for high-level logic where clamping makes sense, like volume controls or progress indicators. Never use `Wrapping` when you expect the value to grow unbounded. Wrapping hides overflow bugs. Pick the type that matches your domain.

## Minimal example

This example shows how to use `NonZero` and `Wrapping` together. It demonstrates the constructor pattern and the arithmetic behavior.

```rust
use std::num::{NonZeroU32, Wrapping};

/// Calculates a wrapped counter and validates a non-zero ID.
fn process(id_input: u32, increment: u8) -> Option<(NonZeroU32, u8)> {
    // Convert the input to NonZeroU32.
    // Returns None if id_input is zero.
    let id = NonZeroU32::new(id_input)?;

    // Create a wrapping counter starting at 250.
    let mut counter = Wrapping(250u8);

    // Add the increment.
    // If increment is 10, 250 + 10 = 260, which wraps to 4.
    counter += Wrapping(increment);

    // Return the validated ID and the wrapped counter value.
    Some((id, counter.0))
}

fn main() {
    // Valid ID, counter wraps.
    if let Some((id, count)) = process(42, 10) {
        println!("ID: {}, Count: {}", id, count);
    }

    // Invalid ID, returns None.
    if let Some((id, count)) = process(0, 10) {
        println!("This won't print");
    } else {
        println!("Rejected zero ID");
    }
}
```

The `?` operator in `process` propagates the `None` if the ID is zero. This keeps the function clean. The `Wrapping` addition handles the overflow automatically. The result is a `Wrapping<u8>`, so you access the inner value with `.0`.

## Realistic example: Entity management

Game engines and simulations often use entity IDs. Zero is usually reserved for "no entity." You want to store entities in a map and ensure IDs are valid. `NonZeroU32` is perfect here.

```rust
use std::num::NonZeroU32;
use std::collections::HashMap;

/// Stores entity data keyed by a non-zero ID.
/// Using NonZeroU32 allows the HashMap to use a compact representation.
struct EntityStore {
    entities: HashMap<NonZeroU32, String>,
}

impl EntityStore {
    fn new() -> Self {
        Self {
            entities: HashMap::new(),
        }
    }

    /// Adds an entity. Returns false if the ID is zero.
    fn add(&mut self, id: u32, name: String) -> bool {
        // Try to convert u32 to NonZeroU32.
        // If id is 0, this returns None and we reject the insert.
        if let Some(nz_id) = NonZeroU32::new(id) {
            self.entities.insert(nz_id, name);
            true
        } else {
            false
        }
    }

    /// Gets an entity by ID.
    fn get(&self, id: NonZeroU32) -> Option<&String> {
        self.entities.get(&id)
    }
}

fn main() {
    let mut store = EntityStore::new();

    // Add valid entities.
    assert!(store.add(1, "Player".to_string()));
    assert!(store.add(2, "Enemy".to_string()));

    // Attempt to add zero ID. Fails safely.
    assert!(!store.add(0, "Invalid".to_string()));

    // Retrieve an entity.
    let id = NonZeroU32::new(1).unwrap();
    println!("Entity: {:?}", store.get(id));
}
```

The `add` method uses `if let` to handle the conversion. If the ID is zero, the function returns `false`. The map never stores an invalid key. The `get` method takes a `NonZeroU32`, so the caller must have a valid ID. The type system prevents passing zero to `get`.

## Pitfalls and compiler errors

`NonZero` types have friction points. You cannot perform arithmetic directly on them. The `+` operator is not implemented for `NonZeroU32`. If you try to add two `NonZero` values, the compiler rejects you with `E0277` (the trait bound `NonZeroU32: Add` is not satisfied). You must convert to the base integer, do the math, and convert back.

```rust
use std::num::NonZeroU32;

fn main() {
    let a = NonZeroU32::new(10).unwrap();
    let b = NonZeroU32::new(20).unwrap();

    // This fails with E0277.
    // let sum = a + b;

    // Correct approach: convert, add, convert back.
    let sum = NonZeroU32::new(a.get() + b.get());
}
```

The result of arithmetic might be zero. `1 - 1` is `0`. `NonZero` cannot hold zero. The conversion back to `NonZero` returns `Option`. You must handle the case where the result is zero.

`NonZero` does not prevent overflow. `NonZeroU32::new(u32::MAX).unwrap() + 1` overflows. The type only guarantees the value is not zero. It does not guarantee the value is within a safe range for arithmetic. Use `Wrapping` or `Saturating` if you need overflow control.

You cannot pass a `NonZeroU32` where a `u32` is expected without conversion. The compiler rejects this with `E0308` (mismatched types). Use `.get()` to extract the inner value.

```rust
fn takes_u32(val: u32) {
    println!("{}", val);
}

fn main() {
    let nz = NonZeroU32::new(42).unwrap();

    // This fails with E0308.
    // takes_u32(nz);

    // Correct: extract the value.
    takes_u32(nz.get());
}
```

**Convention aside:** Never use `NonZeroU32::new_unchecked` unless you have a mathematical proof the value is non-zero. This function is `unsafe`. It skips the check. If you pass zero, you get undefined behavior. The compiler cannot verify the safety. Reserve `new_unchecked` for FFI boundaries where you trust the external code, or for performance-critical loops where the check is redundant and proven. In 99% of cases, use `new`.

Treat the type as a contract. If the value can be zero, the type system will stop you at the source. Do not fight the compiler with `unwrap`. Handle the `Option`.

## Decision matrix

Use `NonZero` when you need to distinguish zero from valid data, or when you want `HashMap` performance optimizations for integer keys. Use `NonZero` for FFI handles that are guaranteed non-null, like file descriptors or socket IDs. Use `Wrapping` when you expect overflow and want it to wrap silently without panicking, like in checksums, hash functions, or circular buffers. Use `Saturating` when you want overflow to clamp to the max or min value, like in UI counters, volume controls, or progress bars. Use plain integers when you want the compiler to panic on overflow in debug builds, which is the default safe behavior for most logic. Use `Option<T>` when the value might be absent, not just zero. `NonZero` is for "present but not zero," not "maybe absent."

Pick the overflow behavior that matches your domain. Wrapping is for math that cycles. Saturating is for limits. Panicking is for bugs.

## Where to go next

- [How to Use std::net for Networking in Rust](/en/how-to-use-stdnet-for-networking-in-rust/)
- [How to Read Environment Variables in Rust](/en/how-to-read-environment-variables-in-rust/)
- [How to Use nom for Parser Combinators in Rust](/en/how-to-use-nom-for-parser-combinators-in-rust/)