Types and Traits

Guideline: Use strong types to differentiate between logically distinct values gui_xztNdXA2oFNC
status: draft
tags: types, safety, understandability
category: advisory
decidability: undecidable
scope: module
release: 1.85.0;1.85.1

Parameters and variables with logically distinct types must be statically distinguishable by the type system.

Use a newtype (e.g., struct Meters(u32);) when:

  • Two or more quantities share the same underlying primitive representation but are logically distinct

  • Confusing them would constitute a semantic error

  • You need to improve type safety and encapsulation

  • You need to enable trait-based behavior

  • You need to establish new invariants

Rationale: rat_kYiIiW8R2qD2
status: draft
parent needs: gui_xztNdXA2oFNC

This rule ensures that parameters and variables convey intent directly through the type system to avoid accidental misuse of values with identical primitives but different semantics. In particular:

  • Prevents mixing logically distinct values. Primitive types like u32 or u64 can represent lengths, counters, timestamps, durations, IDs, or other values. Different semantic domains can be confused, leading to incorrect computations. The Rust type system prevents such mistakes when semantics are encoded into distinct types.

  • Improves static safety. Statically distinct types allow the compiler to enforce domain distinctions. Accidental swapping of parameters or returning the wrong quantity becomes a compile-time error.

  • Improves readability and discoverability. Intent-revealing names (Meters, Seconds, UserId) make code self-documenting. Type signatures become easier to read and understand.

  • Enables domain-specific trait implementations. Statically distinct types allow you to implement Add, Mul, or custom traits in ways that match the domain logic. Aliases cannot do this, because they are not distinct types.

  • Supports API evolution. Statically distinct types act as strong API contracts that can evolve independently from their underlying representations.

Non-Compliant Example: non_compl_ex_PO5TyFsRTlWw
status: draft
parent needs: gui_xztNdXA2oFNC

This noncompliant example uses primitive types directly, leading to potential confusion between distance and time. Nothing prevents the caller from passing time as distance or vice-versa. The units of each type are not clear from the function signature alone. Mistakes compile cleanly and silently produce wrong results.

fn travel(distance: u32, time: u32) -> u32 {
   distance / time
}

fn main() {
   let d = 100;
   let t = 10;
   let _result = travel(t, d);  // Compiles, but semantically incorrect
}
Non-Compliant Example: non_compl_ex_PO5TyFsRTlWv
status: draft
parent needs: gui_xztNdXA2oFNC

This noncompliant example uses aliases instead of distinct types. Aliases do not create new types, so the compiler cannot enforce distinctions between Meters and Seconds.

Aliases cannot do this, because they are not distinct types. This noncompliant example uses primitive types directly, leading to potential confusion between distance and time. Nothing prevents the caller from passing time as distance or vice-versa. The units of each type are not clear from the function signature alone. Mistakes compile cleanly and silently produce wrong results.

type Meters = u32;
type Seconds = u32;
type MetersPerSecond = u32;

fn travel(distance: Meters, time: Seconds) -> MetersPerSecond {
   distance / time
}

fn main() {
   let d: Meters = 100;
   let t: Seconds = 10;
   let _result = travel(t, d);  // Compiles, but semantically incorrect
}
Compliant Example: compl_ex_WTe7GoPu5Ez1
status: draft
parent needs: gui_xztNdXA2oFNC

This compliant example uses newtypes to create distinct types for Meters, Seconds, and MetersPerSecond. The compiler enforces correct usage, preventing accidental swapping of parameters. The function signature clearly conveys the intended semantics of each parameter and return value.

use std::ops::Div;

#[derive(Debug, Clone, Copy)]
struct Meters(u32);

#[derive(Debug, Clone, Copy)]
struct Seconds(u32);

#[derive(Debug, Clone, Copy)]
struct MetersPerSecond(u32);

impl Div<Seconds> for Meters {
    type Output = MetersPerSecond;

    fn div(self, rhs: Seconds) -> Self::Output {
         MetersPerSecond(self.0 / rhs.0)
    }
 }

 fn main() {
     let d = Meters(100);
     let t = Seconds(10);
     let result = d / t;  // Clean and type-safe!
     println!("{:?}", result);  // MetersPerSecond(10)
 }
Guideline: Ensure reads of union fields produce valid values for the field's type gui_UnionFieldValidity

Ensure that the underlying bytes constitute a valid value for that field’s type when reading from a union field. Reading a union field whose bytes do not represent a valid value for the field’s type is undefined behavior.

Before accessing a union field, verify that that the union was either:

  • last written through that field, or

  • written through a field whose bytes are valid when reinterpreted as the target field’s type

If the active field is uncertain, use explicit validity checks.

status: draft

Similar to C, unions allow multiple fields to occupy the same memory. Unlike enumeration types, unions do not track which field is currently active. You must ensure that when a field is read that the underlying bytes are valid for that field’s type [RUST-REF-UNION].

Every type has a validity invariant — a set of constraints that all values of that type must satisfy [UCG-VALIDITY]. Reading a union field performs a typed read, which asserts that the bytes are valid for the target type.

Examples of validity requirements for common types:

  • bool: Must be 0 (false) or 1 (true). Any other value (e.g., 3) is invalid.

  • char: Must be a valid Unicode scalar value (0x0 to 0xD7FF or 0xE000 to 0x10FFFF).

  • References: Must be non-null and properly aligned.

  • Enums: Must hold a valid discriminant value.

  • Floating point: All bit patterns are valid for the f32 or f64 types.

  • Integers: All bit patterns are valid for integer types.

Reading an invalid value is undefined behavior.

Non-Compliant Example: non_compl_ex_UnionBool
status: draft

This noncompliant example reads an invalid bit pattern from a Boolean union field. The value 3 is not a valid value of type bool (only 0 and 1 are valid).

union IntOrBool {
    i: u8,
    b: bool,
}

fn main() {
    let u = IntOrBool { i: 3 };

    // Undefined behavior reading an invalid value from a union field of type 'bool'
    unsafe { u.b };  // Noncompliant
}
Non-Compliant Example: non_compl_ex_UnionChar
status: draft

This noncompliant example reads an invalid Unicode value from a union field of type char .

union IntOrChar {
    i: u32,
    c: char,
}

fn main() {
    // '0xD800' is a surrogate and not a valid Unicode scalar value
    let u = IntOrChar { i: 0xD800 };

    // Reading an invalid Unicode value from a union field of type 'char'
    unsafe { u.c };  // Noncompliant
}
Non-Compliant Example: non_compl_ex_UnionEnum
status: draft

This noncompliant example reads an invalid discriminant from a union field of ‘Color’ enumeration type.

#[repr(u8)]
#[derive(Copy, Clone)]
enum Color {
    Red = 0,
    Green = 1,
    Blue = 2,
}

union IntOrColor {
    i: u8,
    c: Color,
}

fn main() {
    let u = IntOrColor { i: 42 };

    // Undefined behavior reading an invalid discriminant from the 'Color' enumeration type
    unsafe { u.c };  // Noncompliant
}
Non-Compliant Example: non_compl_ex_UnionRef
status: draft

This noncompliant example reads a reference from a union containing a null pointer. A similar problem occurs when reading a misaligned pointer.

union PtrOrRef {
    p: *const i32,
    r: &'static i32,
}

fn main() {
    let u = PtrOrRef { p: std::ptr::null() };

    //  Undefined behavior reading a null value from a reference field of a union
    unsafe { u.r };  // Noncompliant
}
Compliant Example: compl_ex_UnionTrackField
status: draft

This compliant example tracks the active field explicitly to ensure valid reads.

union IntOrBool {
    i: u8,
    b: bool,
}

enum ActiveField {
    Int,
    Bool,
}

struct SafeUnion {
    data: IntOrBool,
    active: ActiveField,
}

impl SafeUnion {
    fn new_int(value: u8) -> Self {
        Self {
            data: IntOrBool { i: value },
            active: ActiveField::Int,
        }
    }

    fn new_bool(value: bool) -> Self {
        Self {
            data: IntOrBool { b: value },
            active: ActiveField::Bool,
        }
    }

    fn get_bool(&self) -> Option<bool> {
        match self.active {
            // Compliant: only read bool when we know it was written as bool
            ActiveField::Bool => Some(unsafe { self.data.b }),
            ActiveField::Int => None,
        }
    }
}

fn main() {
    let union_bool = SafeUnion::new_bool(true);
    let union_int = SafeUnion::new_int(42);

    println!("Bool union as bool: {:?}", union_bool.get_bool());  // Some(true)
    println!("Int union as bool: {:?}", union_int.get_bool());    // None
}
Compliant Example: compl_ex_UnionSameField
status: draft

This compliant solution reads from the same field that was written.

union IntOrBool {
    i: u8,
    b: bool,
}

fn main() {
    let u = IntOrBool { b: true };

    // Read the same field that was written
    println!("bool value: {}", unsafe { u.b }); // compliant
}
Compliant Example: compl_ex_UnionValidReinterpret
status: draft

This compliant example reinterprets the value as a different types where all bit patterns are valid.

union IntBytes {
    i: u32,
    bytes: [u8; 4],
}

fn main() {
    let u = IntBytes { i: 0x12345678 };

    // All bit patterns are valid for [u8; 4]
    println!("bytes: {:?}", unsafe { u.bytes }); // compliant

    let u2 = IntBytes { bytes: [0x11, 0x22, 0x33, 0x44] };

    // All bit patterns are valid for 'u32'
    println!("integer: 0x{:08X}", unsafe { u2.i }); // compliant
}
Compliant Example: compl_ex_UnionValidateBool
status: draft

This compliant example validates bytes before reading as a constrained type.

union IntOrBool {
    i: u8,
    b: bool,
}

fn try_read_bool(u: &IntOrBool) -> Option<bool> {
    // Read as integer (always valid for 'u8')
    let raw = unsafe { u.i };

    // Validate before interpreting as a value of type 'bool'
    match raw {
        0 => Some(false),
        1 => Some(true),
        _ => None,  // Invalid Boolean value
    }
}

fn main() {
    let u1 = IntOrBool { i: 1 };
    let u2 = IntOrBool { i: 3 };

    // Validates before reading as value of type 'bool'
    println!("u1 as bool: {:?}", try_read_bool(&u1));  // Some(true)
    println!("u2 as bool: {:?}", try_read_bool(&u2));  // None
}
Bibliography: bib_UnionFieldValidity
status: draft
The Rust Project Developers. “Rust Reference: Unions.”
The Rust Reference, n.d.
Rust Unsafe Code Guidelines Working Group. “Validity and Safety Invariant.”
Rust Unsafe Code Guidelines, n.d.