Rust type categories
December 17, 2024
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.
- 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]);
- 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.
- 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);
}