🏠 Home BETTER LATE THAN NEVER
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);
}