[Rust Guide] 5.1. Defining and Instantiating Structs

rust dev.to

If you find this helpful, please like, bookmark, and follow. To keep learning along, follow this series.

5.1.1 What Is a Struct

The meaning of struct is “structure”. It is a custom data type that allows programs to name and bundle related values into meaningful combinations. It is similar to a “class” or “structure” in other programming languages, but it only provides data storage and does not include methods.

People who have studied C/C++ may already be very familiar with the struct keyword, but there are differences:

  • C: struct is a simple aggregate type used to organize data. It can contain only data and no methods.

  • C++: struct is very similar to class. It can contain data and methods, and the only syntax difference is that the default access level in struct is public, while in class it is private.

  • Rust: struct is used only to define data structures and does not include methods. Methods must be defined for the struct through an impl block. Rust provides stricter ownership, lifetime, and memory management mechanisms.

5.1.2 Defining a Struct

  • Use the struct keyword to name the entire struct using CamelCase.
  • Inside curly braces, define the name and type of every field.

Example:
Create a struct customized to store various data for CS professional players on HLTV (additional info: CS professional player data generally consists of Rating, DPR, KAST, Impact, ADR, and KPR).

struct Stats{  
    rating: f32,  
    dpr: f32,  
    kast: f32,  
    impact: f32,  
    adr: f32,  
    kpr: f32,  
}
Enter fullscreen mode Exit fullscreen mode

5.1.3 Instantiating a Struct

To use a struct, you need to create an instance of it:

  • Assign a concrete value to each field; you cannot omit field values.
  • There is no need to specify them in the order in which they were declared.

Using donk as an example, create his database:

fn main() {  
    let donk = Stats {  
        rating: 1.27,  
        impact: 1.4,  
        dpr: 0.67,  
        adr: 88.8,  
        kast: 74.1,  
        kpr: 0.85,  
    };  
}
Enter fullscreen mode Exit fullscreen mode

5.1.4 Accessing the Value of a Field in a Struct

You can use dot notation to access a field’s value in a struct:

fn main() {  
    let mut donk = Stats {  
        rating: 1.27,  
        impact: 1.4,  
        dpr: 0.67,  
        adr: 88.8,  
        kast: 74.1,  
        kpr: 0.85,  
    };  
    donk.rating = 2.59;  
}
Enter fullscreen mode Exit fullscreen mode

If you want to change a struct’s values, remember to use the mutable variable keyword mut when instantiating it.

In a struct, the smallest unit of mutability is the entire instance, so you cannot control the mutability of a single field on its own. Once a struct instance is declared mutable, all fields in that instance are mutable.

5.1.5 Using a Struct as a Function Return Value

The last expression in a function is its return value, so if you use a struct as a return value, you only need to make sure that constructing the struct is the last expression in the function (without a semicolon):

fn change_stats(rating: f32, impact:f32, dpr:f32, adr:f32, kast:f32, kpr:f32) -> Stats{  
    Stats {  
        rating: rating,  
        impact: impact,  
        dpr: dpr,  
        adr: adr,  
        kast: kast,  
        kpr: kpr,  
    }  
}
Enter fullscreen mode Exit fullscreen mode

5.1.6 Field Init Shorthand

Rust, like JS and C#, allows field initialization to be shortened in some cases.

When a field name and the corresponding variable name for the field value are the same, you can use shorthand. For example, in the previous code snippet, all field names are the same as the variable names for their values, so it can be shortened to:

fn change_stats(rating: f32, impact:f32, dpr:f32, adr:f32, kast:f32, kpr:f32) -> Stats{  
    Stats {  
        rating,  
        impact,  
        dpr,  
        adr,  
        kast,  
        kpr,  
    }  
}
Enter fullscreen mode Exit fullscreen mode

Of course, this is not limited to cases where everything matches. As long as one field meets the shorthand condition, you can use the shorthand there and keep the normal syntax for the others.

5.1.7 Struct Update Syntax

When you create a new instance based on an existing struct instance, and the new instance has fields that are the same as the old one, you can use update syntax.

For example, if I want to create data for sh1ro, where his rating is 1.25, his impact is 1.2, and the rest are the same as donk’s, this is the basic form:

fn main() {  
    let donk = Stats {  
        rating: 1.27,  
        impact: 1.4,  
        dpr: 0.67,  
        adr: 88.8,  
        kast: 74.1,  
        kpr: 0.85,  
    };  
    let sh1ro = Stats {  
        rating: 1.25,  
        impact: 1.2,  
        dpr: donk.dpr,  
        adr: donk.adr,  
        kast: donk.kast,  
        kpr: donk.kpr,
    };  
}
Enter fullscreen mode Exit fullscreen mode

This is a bit cumbersome, so Rust provides this syntactic sugar:

fn main() {  
    let donk = Stats {  
        rating: 1.27,  
        impact: 1.4,  
        dpr: 0.67,  
        adr: 88.8,  
        kast: 74.1,  
        kpr: 0.85,  
    };  
    let sh1ro = Stats {  
        rating: 1.25,  
        impact: 1.2,  
        ..donk  
    };  
}
Enter fullscreen mode Exit fullscreen mode

You only need to write the parts that changed. For the rest, just write .. followed by the name of the other struct instance, which means that the values of the remaining unassigned fields are the same as the corresponding fields in the other instance.

5.1.8 Tuple Structs

A tuple struct is a type of struct that is similar to a tuple. The whole tuple struct has a name, but the elements inside it do not. It is useful when you want to name an entire tuple, make it distinct from other tuples, and do not need to name each element.

To define a tuple struct, use the struct keyword followed by the name and the types of the elements inside it.

Example:

struct Color(u8, u8, u8);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
Enter fullscreen mode Exit fullscreen mode

Some people jokingly say that tuple structs have no equivalent in traditional programming languages and come from the noble lineage of Haskell. This is because in many traditional object-oriented languages, such as Java and C++, structs or classes are named and have named fields, while tuples are anonymous and based only on order. There is no intermediate form that combines the strengths of both. Rust’s tuple struct concept is directly related to Haskell’s Newtype Pattern. In Haskell, you can define a similar pattern with newtype.

It is worth noting that even if two tuple structs have the same number of elements and the corresponding element types are identical, they should not be considered the same type, because they are different structs.

5.1.9 Unit-Like Structs

They behave similarly to the unit type (). They are used when you need a type marker or want to implement a trait on some type (which you can think of as an interface) without storing any data in the type itself. This is similar to interface{} in Go.

struct ReadOnly;
struct WriteOnly;

fn process_data<T>(_mode: T) {
    // Used only as a type marker
}

fn main() {
    process_data(ReadOnly);
    process_data(WriteOnly);
}
Enter fullscreen mode Exit fullscreen mode

This example implements type markers.

5.1.10 Ownership of Struct Data

struct User { 
active: bool, 
username: String, 
email: String, 
sign_in_count: u64, 
}
Enter fullscreen mode Exit fullscreen mode

In this example, both username and email use the String type instead of &str, because String is an owned type and owns all of its data. In this case, as long as the instance is valid, the field data inside it is also definitely valid.

Reference types such as &str can also be stored in a struct, but that requires lifetimes (which we will cover later). Simply put, lifetimes ensure that as long as the struct instance is valid, the references inside it are also valid. If a struct stores references without using lifetimes, it will produce an error (missing lifetime specifier).

Read Full Tutorial open_in_new
arrow_back Back to Tutorials