Skip to main content

Using Structs to Structure Related Data

Defining and instantiating structs

Struct allows you to compose different type of data together into one big object, just like structs in C.

You will have to name each piece of data that you are using so that you can access them when you instantiate a struct later.

Here is how to define a sample struct (Note you would write this outside of functions):

struct User {
	active: bool,
    username: String,
    email: String,
}

Then to instantiate a struct:

fn main() {
	let user1 = User {
    	active: true,
        username: String::from("Ricky"),
        email: String::from("irebo@gmail.com"),
	};
}

To access the fields that you have instantiated you would use the dot notation, user1.active, user1.username, user1.email.

To make the struct mutable you would also attach the mut modifier to the variable. The entire instance of struct must be mutable, Rust doesn't allow partial field mutability.

Field init shorthand

Say you have a function that builds your struct and return it as its return value depending on the parameter you passed:

fn build_user(email: String, username: String) -> User {
	User {
    	active: true,
        username: username,
        email: email,
	}
}

Writing it like this will get repetitive, especially if there are going to be lot of parameter, instead you can use a shorthand, just ignore the key if the parameter that you passed into the function is the same as the key name:

fn build_user(email: String, username: String) -> User {
    User {
        active: true,
        username,
        email,
    }
}

This is much more concise without the repetition.

Creating instances from other instances with struct update syntax

Sometimes you might want to create a new instances from the old instances, changing some of the old values but keep the rest the same.

You can do it the hard coded way like such:

fn main() {
	// created user1 here
    
    let user2 = User {
    	active: user1.active,
        username: user1.username,
        email: String::from("new email@gmail.com"),
	};
}

This works but you have to type out all of the fields that are repeated, a much shorter way to do this is to use struct update syntax:

fn main() {
	// create user1 here
    
    let user2 = User {
    	email: String::from("new email here"),
        ..user1
	}
}

With this syntax, you only have to worry about writing the new value for the new instance, and leave all of the old values to struct update syntax to handle.

..user1 must come last to specify that any remaining fields should be getting their values from the corresponding fields in user1.

struct update syntax uses = like an assignment, so it will be moving data. After doing struct update you can no longer use user1 as a whole after creating user2 since data like username is moved to user2 and not copied!

Tuple struct

You can also create a tuple struct, which is like struct but doesn't have names associated with their fields, they only have type of the fields.

This is useful if you just want to give a simple tuple a name. And separate different type of tuple from each other.

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
	let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}

You can access the tuple using the same tuple syntax, tuple.<index number>