Sling Academy
Home/Rust/Defining Custom Data Types with Rust Structs

Defining Custom Data Types with Rust Structs

Last updated: January 03, 2025

Rust, a systems programming language loved for its safety and performance, offers several ways to define and work with custom data types. One of the most commonly used is the struct. Rust structs allow you to group together related data and easily manage it. This article will guide you through the fundamentals of defining and using structs in Rust, enabling you to structure data in your applications effectively.

Understanding Structs

Structs are similar to classes in object-oriented programming languages, but they don't involve methods for encapsulation and inheritance. They are, in essence, a way of grouping together different bits of data, known as fields, into a single, neat package that you can pass around.

Defining a Struct

To define a struct in Rust, you use the struct keyword followed by a name for your struct and a set of curly braces containing the fields you want to include. Here's an example:

struct Rectangle {
    width: u32,
    height: u32,
}

In this example, we've defined a Rectangle struct with two fields: width and height, both of type u32 (an unsigned 32-bit integer).

Creating an Instance of a Struct

Once you've defined a struct, you can create an instance of it by specifying concrete values for each of its fields. For example:

fn main() {
    let my_rectangle = Rectangle { width: 30, height: 50 };
    println!("The rectangle is {} pixels wide and {} pixels tall.", my_rectangle.width, my_rectangle.height);
}

In this case, we create an instance of Rectangle named my_rectangle with a width of 30 pixels and a height of 50 pixels.

Using Structs with Methods

Although Rust treats structs differently than object-oriented languages treat their objects, you can still associate methods with structs. This is done through implementing the impl block. Let's see how to add a method to calculate the area of our rectangle:

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

And you can use this method as follows:

fn main() {
    let rect = Rectangle { width: 30, height: 50 };
    println!("The area of the rectangle is {} square pixels.", rect.area());
}

Here, the method area takes a reference to self and returns the product of width and height, thus calculating the area.

Structes with Named vs Tuple Structs

Structs in Rust are categorized into named structs, as we've seen above, and tuple structs, which resemble tuples themselves but are distinct data types. Here's how a tuple struct looks:

struct Color(i32, i32, i32);

To create an instance of a tuple struct:

fn main() {
    let black = Color(0, 0, 0);
    println!("RGB values for black are: {}, {}, {}.", black.0, black.1, black.2);
}

Tuple structs offer a simpler way to define structs with fewer fields, especially when field names aren't necessary or self-descriptive.

Structs and Ownership

One of Rust's key features is its ownership model, and structs embrace this model by ensuring that field values follow the same ownership, borrowing, and referencing rules as other Rust variables. For complex interactions, understanding ownership is crucial to avoid unexpected behavior or compile-time errors.

This is just scratching the surface of what you can do with structs in Rust. Utilizing them correctly allows for more efficient and cleaner code organization, which is particularly beneficial when managing large-scale projects. Additionally, with other Rust characteristics like enum, pattern matching, and the borrowing concept, you're well on your way to adopting a truly powerful language paradigm.

Next Article: Managing Strings in Rust: `String` vs `&str`

Previous Article: Enums in Rust: Flexible Variants for Safer Code

Series: Rust Data Types

Rust

You May Also Like

  • E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel
  • Network Protocol Handling Concurrency in Rust with async/await
  • Using the anyhow and thiserror Crates for Better Rust Error Tests
  • Rust - Investigating partial moves when pattern matching on vector or HashMap elements
  • Rust - Handling nested or hierarchical HashMaps for complex data relationships
  • Rust - Combining multiple HashMaps by merging keys and values
  • Composing Functionality in Rust Through Multiple Trait Bounds
  • E0437 in Rust: Unexpected `#` in macro invocation or attribute
  • Integrating I/O and Networking in Rust’s Async Concurrency
  • E0178 in Rust: Conflicting implementations of the same trait for a type
  • Utilizing a Reactor Pattern in Rust for Event-Driven Architectures
  • Parallelizing CPU-Intensive Work with Rust’s rayon Crate
  • Managing WebSocket Connections in Rust for Real-Time Apps
  • Downloading Files in Rust via HTTP for CLI Tools
  • Mocking Network Calls in Rust Tests with the surf or reqwest Crates
  • Rust - Designing advanced concurrency abstractions using generic channels or locks
  • Managing code expansion in debug builds with heavy usage of generics in Rust
  • Implementing parse-from-string logic for generic numeric types in Rust
  • Rust.- Refining trait bounds at implementation time for more specialized behavior