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 literalconst user = { username: "rustacean", email: "rust@example.com", active: true, signupDate: new Date()};
// JavaScript classclass User { constructor(username, email) { this.username = username; this.email = email; this.active = true; this.signupDate = new Date(); }}
In Rust, we use structs:
// Rust struct definitionstruct 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 creationconst user1 = { username: "rustacean", email: "rust@example.com", active: true, signupDate: new Date()};
// JavaScript class instantiationconst user2 = new User("rustacean", "rust@example.com");
In Rust:
// Creating an instance of a structlet 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 namesfunction 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 emailconst 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 colorstruct 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.