Rust type categories

Rust type categories

December 17, 2024
rust
Notes for the rust type categories

For rust fundamental types, the serde crate serializer and deserializer provide a reasonable type categories for grouping the complex types when serializing or deserializing a type.

Unit struct and unit variant #

Unit Struct #

A unit struct is a struct with no fields. It is defined using empty parentheses or simply by omitting fields.

  • Lightweight and zero-sized.
  • Typically used as a marker or tag type.
  • Useful for implementing traits or carrying type-level information.
struct UnitStruct; // No fields

Example Usage:

struct Marker; // Unit struct

fn main() {
    let _m = Marker; // Creates an instance of `Marker`
}

Use Case: Implementing Traits

Unit structs are often used for implementing traits that don’t require data.

struct Logger; // Unit struct

trait Log {
    fn log_message(&self, msg: &str);
}

impl Log for Logger {
    fn log_message(&self, msg: &str) {
        println!("Log: {}", msg);
    }
}

fn main() {
    let logger = Logger;
    logger.log_message("Hello, world!");
}

Zero-Sized Types (ZST)

Unit structs have zero size in memory, as they carry no data.

struct Empty;

fn main() {
    println!("Size of Empty: {}", std::mem::size_of::<Empty>()); // 0
}

Unit Variant #

A unit variant is an enum variant with no fields. It is equivalent to a constant and is often used to represent distinct states or options, It is very like to the C++ enum.

  • A lightweight enum variant.
  • Useful for enumerating states, flags, or options.
  • Acts like a named constant within the enum.
enum MyEnum {
    UnitVariant, // No fields
}

Example usage

enum Status {
    Success,
    Failure,
    Pending,
}

fn print_status(status: Status) {
    match status {
        Status::Success => println!("Success!"),
        Status::Failure => println!("Failure!"),
        Status::Pending => println!("Pending..."),
    }
}

fn main() {
    let status = Status::Success;
    print_status(status);
}

Newtype struct and Newtype variant #

Newtype Struct #

A newtype struct is a tuple struct with a single field. It is used to give an existing type a new, distinct name. The key features are

  • Encapsulation: Provides a distinct type that prevents accidental misuse.
  • Zero-cost abstraction: No runtime overhead – it’s as efficient as the underlying type.
  • Safety: We can’t mix UserId with plain u32 accidentally.
struct NewType(T); // T is the underlying type

Example

struct UserId(u32); // Newtype struct wrapping `u32`

fn display_user(id: UserId) {
    println!("User ID: {}", id.0); // Access the wrapped value
}

fn main() {
    let id = UserId(1001);
    display_user(id);
    // display_user(1001); // Error: type mismatch
}

Newtype Variant #

A newtype variant is an enum variant that wraps a single value (like a tuple struct inside an enum). It is often used to add context or differentiate between variants.

enum MyEnum {
    Variant(T), // T is the wrapped type
}

Example

enum Response {
    Success(String),  // Newtype variant wrapping a `String`
    Error(u32),       // Newtype variant wrapping a `u32`
}

fn handle_response(res: Response) {
    match res {
        Response::Success(msg) => println!("Success: {}", msg),
        Response::Error(code) => println!("Error with code: {}", code),
    }
}

fn main() {
    let ok = Response::Success("All good!".to_string());
    let err = Response::Error(404);

    handle_response(ok);
    handle_response(err);
}

Tuple struct and Tuple variant #

Tuple Struct #

A tuple struct is a struct with unnamed fields. It groups multiple values (possibly of different types) without explicitly naming each field.

struct TupleStructName(T1, T2, ...);
  • T1, T2… are the types of the fields.
  • Fields are unnamed and accessed by their position (.0, .1, etc.).

Example

struct Point(i32, i32);

fn main() {
    let p = Point(10, 20);
    println!("x: {}, y: {}", p.0, p.1);
}

Tuple Variant #

A tuple variant is a variant of an enum that holds unnamed fields, similar to a tuple. Tuple variants allow you to associate multiple values with a specific enum case.

enum EnumName {
    VariantName(T1, T2, ...),
}
  • Each variant can hold a different number or type of fields.
  • Fields are unnamed and accessed in a match statement by destructuring.

Example

enum Shape {
    Circle(f64),          // Tuple variant with one field (radius)
    Rectangle(f64, f64),  // Tuple variant with two fields (width, height)
}

fn describe_shape(shape: Shape) {
    match shape {
        Shape::Circle(radius) => println!("Circle with radius: {}", radius),
        Shape::Rectangle(w, h) => println!("Rectangle with width: {} and height: {}", w, h),
    }
}

fn main() {
    let circle = Shape::Circle(5.0);
    let rect = Shape::Rectangle(4.0, 6.0);

    describe_shape(circle);
    describe_shape(rect);
}

Newtype struct/variant vs. Tuple struct/variant #

The terms newtype struct, tuple struct, newtype variant, and tuple variant often overlap in Rust, but they have subtle differences in their intent and use cases. Here’s a clear breakdown:

Newtype Struct vs Tuple Struct #

Both newtype structs and tuple structs use the same tuple struct syntax, but their intent is different:

Aspect Newtype Struct Tuple Struct
Definition A tuple struct with one field A tuple struct with multiple fields
Purpose Create a distinct type alias for a single value Bundle multiple fields without names
Syntax struct NewType(T); struct TupleStruct(T1, T2, ...);
Example struct UserId(u32); struct Point(i32, i32);
Primary Use Case Type safety or a zero-cost wrapper for a single value Lightweight grouping of multiple values
Field Access Accessed via .0 Accessed via .0, .1, etc.

Newtype Struct Example:

The focus is on type safety and wrapping a single type to make it distinct.

struct UserId(u32); // Newtype struct wrapping a single `u32`

fn print_user_id(id: UserId) {
    println!("User ID: {}", id.0);
}

fn main() {
    let id = UserId(42);
    print_user_id(id);
    // print_user_id(42); // Error: type mismatch!
}

Tuple Struct Example:

The focus is on grouping multiple fields together without names.

struct Point(i32, i32);

fn main() {
    let p = Point(10, 20);
    println!("Point x: {}, y: {}", p.0, p.1); // Access via position
}

Newtype Variant vs Tule Variant #

Both newtype variants and tuple variants are defined inside an enum, but their intent differs:

Aspect Newtype Variant Tuple Variant
Definition Enum variant wrapping a single value Enum variant wrapping multiple values
Purpose Differentiate between a single value in an enum Group multiple fields in a variant
Syntax Variant(T) Variant(T1, T2, ...)
Example enum Result { Ok(u32) } enum Shape { Rectangle(f64, f64) }
Primary Use Case Provide context for a single value Bundle multiple fields for a specific case
Field Access Extracted using match Extracted using match and positional fields

Newtype Variant Example: The focus is on associating a single value with a specific variant.

enum Response {
    Success(String), // Newtype variant with one field
    Error(u32),      // Newtype variant with one field
}

fn handle_response(res: Response) {
    match res {
        Response::Success(msg) => println!("Success: {}", msg),
        Response::Error(code) => println!("Error with code: {}", code),
    }
}

fn main() {
    let res1 = Response::Success("Operation complete".to_string());
    let res2 = Response::Error(404);

    handle_response(res1);
    handle_response(res2);
}
  • Response::Success and Response::Error are newtype variants because they each wrap a single value.
  • This is useful for simple enum cases with one field.

Tuple Variant Example:

The focus is on associating multiple fields with an enum variant.

enum Shape {
    Circle(f64),             // Single value (radius)
    Rectangle(f64, f64),     // Tuple variant with two fields
}

fn describe_shape(shape: Shape) {
    match shape {
        Shape::Circle(radius) => println!("Circle with radius: {}", radius),
        Shape::Rectangle(w, h) => println!("Rectangle with width {} and height {}", w, h),
    }
}

fn main() {
    let circle = Shape::Circle(5.0);
    let rectangle = Shape::Rectangle(4.0, 6.0);

    describe_shape(circle);
    describe_shape(rectangle);
}
  • Shape::Rectangle is a tuple variant because it has two unnamed fields.
  • This is useful when multiple values are required for a variant.

Tuple and Sequence #

In Rust, tuples and sequences (like Vec or arrays) are both ways to group multiple values, but they differ significantly in their structure, purpose, and usage.

Tuple: Fixed Heterogeneous Grouping #

  • Fixed Size: The size of a tuple is known at compile time and cannot grow or shrink.
  • Heterogeneous: Tuples can contain different types of values.
  • Indexed Access: Fields are accessed using their position (.0, .1, etc.).
  • No Iteration: Tuples do not implement IntoIterator directly.

Syntax:

let tuple = (42, "hello", 3.14);

Example:

let person: (&str, i32) = ("Alice", 30);

println!("Name: {}", person.0);
println!("Age: {}", person.1);

Sequence: Homogeneous Collections #

A sequence in Rust refers to collections like Vec, arrays, and slices. They are designed for homogeneous data.

  1. Arrays

Fixed-size, homogeneous collections stored on the stack.

let arr: [i32; 3] = [1, 2, 3];
let nums = [1, 2, 3];
println!("First: {}", nums[0]);
println!("Second: {}", nums[1]);
  1. Vectors(Vec)

Dynamic, growable arrays stored on the heap.

let vec = vec![1, 2, 3];
let mut numbers = Vec::new();
numbers.push(10);
numbers.push(20);

println!("Numbers: {:?}", numbers);
  • Supports dynamic resizing.
  • Implements IntoIterator for iteration.
  1. Slices

A view into a contiguous block of memory.

let slice = &arr[0..2]; // Slice of an array
let arr = [1, 2, 3, 4];
let slice = &arr[1..3];

println!("{:?}", slice); // [2, 3]

Tuple vs Sequence #

Feature Tuple Sequence (Vec, Array, Slice)
Size Fixed at compile time Varies (for Vec) or fixed (array)
Homogeneity Heterogeneous Homogeneous
Access Positional: .0, .1, … Index-based: [i]
Iteration No direct iteration Supports iteration (e.g., for)
Ownership Stack-allocated Stack (array) or heap (Vec)
Common Use Cases Temporary grouping, return values Storing collections of the same type

Tuple (Heterogeneous) Example:

let point: (i32, f64, &str) = (10, 3.5, "origin");

println!("x: {}", point.0);
println!("y: {}", point.1);
println!("label: {}", point.2);

Vector (Homogeneous) Example:

let numbers = vec![1, 2, 3, 4];
for num in &numbers {
    println!("{}", num);
}