Skip to content

Defining Structs in Rust

Structs in Rust are similar to objects and classes in JavaScript, but with important differences in how they’re defined and used. They’re a fundamental way to create custom data types for your program.

What are Structs?

A struct (short for “structure”) is a custom data type that lets you package together and name multiple related values that make up a meaningful group. If you’re coming from JavaScript, you can think of structs as being similar to objects or as the data portion of classes.

JavaScript vs Rust: Data Structures

Let’s compare how we define structured data in both languages:

// JavaScript object literal
const user = {
username: "rustacean",
email: "rust@example.com",
active: true,
signupDate: new Date()
};
// JavaScript class
class User {
constructor(username, email) {
this.username = username;
this.email = email;
this.active = true;
this.signupDate = new Date();
}
}

In Rust, we use structs:

// Rust struct definition
struct User {
username: String,
email: String,
active: bool,
sign_up_date: chrono::DateTime<chrono::Utc>,
}

The key differences are:

  • Rust requires explicit type annotations for all fields
  • Rust uses the struct keyword to define a new structure
  • Fields are separated by commas in JavaScript, by semicolons in Rust
  • You can’t just create a struct on the fly like a JavaScript object literal

Creating Instances of Structs

Let’s see how we create instances in both languages:

// JavaScript object creation
const user1 = {
username: "rustacean",
email: "rust@example.com",
active: true,
signupDate: new Date()
};
// JavaScript class instantiation
const user2 = new User("rustacean", "rust@example.com");

In Rust:

// Creating an instance of a struct
let user = User {
username: String::from("rustacean"),
email: String::from("rust@example.com"),
active: true,
sign_up_date: chrono::Utc::now(),
};

Notice that in Rust:

  • You specify the struct name followed by curly braces
  • You must provide values for all fields (unless you use struct update syntax)
  • Each field is assigned with a colon, and fields are separated by commas
  • The entire struct instantiation is an expression (ends with a semicolon)

Field Init Shorthand

Both JavaScript and Rust provide a shorthand when variable names match field names:

// JavaScript shorthand property names
function createUser(username, email) {
return {
username, // Same as username: username
email, // Same as email: email
active: true,
signupDate: new Date()
};
}

Rust has a similar feature:

fn build_user(username: String, email: String) -> User {
User {
username, // Same as username: username
email, // Same as email: email
active: true,
sign_up_date: chrono::Utc::now(),
}
}

Struct Update Syntax

JavaScript’s spread operator allows copying properties from one object to another:

const user1 = {
username: "rustacean",
email: "rust@example.com",
active: true,
signupDate: new Date()
};
// Create user2 based on user1, overriding the email
const user2 = {
...user1,
email: "new@example.com"
};

Rust has similar functionality with struct update syntax:

let user2 = User {
email: String::from("new@example.com"),
..user1 // Use the rest of values from user1
};

An important distinction: Rust’s update syntax moves data that doesn’t implement the Copy trait, which means you might not be able to use user1 after creating user2 if user1 contains types like String that don’t implement Copy.

Tuple Structs

Rust has a special kind of struct called a tuple struct, which is a combination of a tuple and a struct:

struct Color(i32, i32, i32); // RGB color
struct Point(i32, i32, i32); // 3D point
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);

Tuple structs are useful when you want to give a tuple a name and make it a different type from other tuples, but don’t want to name each field.

Unit-Like Structs

Rust allows structs without any fields, called unit-like structs:

struct AlwaysEqual;
let subject = AlwaysEqual;

These are useful when you need to implement a trait on some type but don’t have any data to store in the type itself.

Ownership of Struct Data

A key difference from JavaScript is that Rust enforces ownership rules with structs:

struct User {
username: String, // This struct owns these Strings
email: String,
}

The struct above owns all of its data. When the struct goes out of scope, all its data is freed, including the heap-allocated strings.

If you want a struct to store references to data owned elsewhere, you’ll need to use lifetimes (which we’ll cover later):

struct UserRef<'a> {
username: &'a str, // This struct borrows these strings
email: &'a str,
}

JavaScript Classes vs Rust Structs + Impl

In JavaScript, classes combine data and methods:

class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
area() {
return this.width * this.height;
}
}
const rect = new Rectangle(10, 5);
console.log(rect.area()); // 50

In Rust, data (structs) and behavior (methods) are separated, with methods defined in impl blocks:

struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
let rect = Rectangle { width: 10, height: 5 };
println!("Area: {}", rect.area()); // Area: 50

We’ll dive deeper into methods in the upcoming “Method Syntax” section.

Field Visibility

JavaScript uses the # symbol for private fields in classes:

class User {
#passwordHash; // Private field
constructor(username, password) {
this.username = username; // Public field
this.#passwordHash = hashPassword(password);
}
}

Rust uses its module system to control visibility with the pub keyword:

pub struct User {
pub username: String, // Public field
email: String, // Private field (within the module)
password_hash: String, // Private field
}

By default, struct fields are private to the module they’re defined in.

Comparing Structs

JavaScript objects can be compared with === but this only checks if they are the same object reference:

const obj1 = { x: 1, y: 2 };
const obj2 = { x: 1, y: 2 };
console.log(obj1 === obj2); // false (different objects)

Rust doesn’t have built-in equality for structs. You need to implement or derive traits like PartialEq to enable comparison:

#[derive(Debug, PartialEq)]
struct Point {
x: i32,
y: i32,
}
let p1 = Point { x: 1, y: 2 };
let p2 = Point { x: 1, y: 2 };
println!("{}", p1 == p2); // true (same values)

Printing Structs

In JavaScript, objects are automatically converted to strings when needed:

const rect = { width: 10, height: 5 };
console.log(rect); // { width: 10, height: 5 }

In Rust, you need to implement or derive the Debug or Display traits:

#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
let rect = Rectangle { width: 10, height: 5 };
println!("{:?}", rect); // Rectangle { width: 10, height: 5 }
println!("{:#?}", rect); // Pretty print

When to Use Structs

Use structs when:

  • You need to group related data together
  • You want to name each piece of data
  • You need a custom data type with specific behavior
  • You want to build up abstractions in your code

In the next section, we’ll explore structs in more depth with a practical example.

Next Steps

Now that you’ve learned how to define structs in Rust, let’s move on to Method Syntax to understand how to implement methods for our structs. This is where we’ll see how Rust’s approach to attaching behavior to data compares to JavaScript’s class-based approach.