Hello! In this lesson, we will dive into one of Rust's fundamental features — structs. Structs are a powerful way to package related data together, making your code more organized and easier to manage.
In this lesson, we will cover how to define structs, create instances, access and modify their fields. By the end of this lesson, you will have a solid understanding of Rust structs and will be ready to use them in your own projects.
Let's get started!
Rust supports many features associated with object-oriented programming (OOP), but it doesn't strictly adhere to traditional OOP principles as seen in languages like Java or C++. Let's take a look at some common features of OOP.
Objects and Classes
Rust does not have a concept of classes or objects as seen in other programming languages. However, Rust allows the grouping of data and functionality into a single data structure called a struct
, similar to an object.
Encapsulation
Rust also supports another OOP paradigm known as encapsulation. The concept of encapsulation helps in managing complexity by hiding the internal state of the object from the outside world and exposing only what is necessary through a defined interface.
Inheritance
Rust does not support inheritance. Inheritance allows a class to derive properties and behavior (methods) from another class. The class that inherits is called the "subclass," and the class being inherited from is called the "superclass." Rust does not have any features that allow a struct
to inherit fields or methods from other structs.
Polymorphism
Rust supports polymorphism using traits
and generics
which we will cover later in this course. Polymorphism allows objects of different types to be treated as objects of a common supertype.
A struct (short for "structure") in Rust is a custom data type that allows you to group together related data. Imagine you are writing a program to manage a library. You will need to keep track of various information about each book, such as its title, author, and the number of pages. Instead of using separate variables for each attribute, Rust allows you to group these related pieces of data into a single, cohesive unit called a struct
. Let's explore how to define a struct
using the book analogy.
In Rust, a struct is defined using the struct
keyword, followed by the struct's name and a block of fields. Each field has a name and a type. Here’s an example to illustrate:
Rust1// Defining a struct 2struct Book { 3 title: String, 4 author: String, 5 pages: u32, 6}
In this code, we defined a struct named Book
with three fields: title
, author
, and pages
. Each field has a specific type: String
for title
and author
, and u32
for pages
. Note that when defining a struct, the last field can optionally end with a comma, and no semicolon is needed after the definition.
Now that we have defined the Book
struct, we can create an instance of it. To do this, we declare a variable and assign a value to each of the fields. We can then access each field using dot notation. Let's take a look.
Rust1fn main() { 2 // Creating an instance of a struct 3 let book1 = Book { 4 title: String::from("Rust Programming"), 5 author: String::from("John Doe"), 6 pages: 250, 7 }; 8 9 // Accessing fields 10 println!("Title: {}", book1.title); // Prints: Title: Rust Programming 11 println!("Author: {}", book1.author); // Prints: Author: John Doe 12 println!("Pages: {}", book1.pages); // Prints: Pages: 250 13}
In this example:
- We create an instance named
book1
with specific values fortitle
,author
, andpages
. - We access the fields using dot notation (e.g.,
book1.title
) and print their values.
Struct fields in Rust are immutable by default. To change their values, we need to create a mutable instance of the struct. Just like other data structures, we use the mut
keyword to make the struct mutable. Here’s how it’s done:
Rust1fn main() { 2 // Create a mutable instance of Book 3 let mut book2 = Book { 4 title: String::from("Rust for Beginners"), 5 author: String::from("John Doe"), 6 pages: 300, 7 }; 8 9 // Modify the author field 10 book2.author = String::from("Cosmo"); 11 println!("Book 2 Author: {}", book2.author); // Prints: Book 2 Author: Cosmo 12}
In this example:
- We use the
mut
keyword to makebook2
mutable. - We change the value of the
author
field and print the updated value.
Each piece of data in Rust has a variable that is its owner.
- The variable assigned to the struct owns the entire struct.
- Each field in the struct does not "own" its data; instead, the struct instance as a whole owns all of its fields.
When you assign the value of one variable to another, the first variable will no longer hold that value if its type does not implement the Copy
trait.
- Structs do not implement the
Copy
trait, even if all of their fields areCopy
. - If a field in the struct holds data that implements the
Copy
trait, assigning a variable to the value of that field will copy that field's data instead of transferring ownership. - If a field holds non-
Copy
data, assigning a variable to the value in that field transfers ownership of that field's data. - Once ownership of a struct is transferred, it is no longer valid to use the original variable, even though other fields are technically still valid within the new owner.
Rust1struct Book { 2 title: String, 3 author: String, 4 pages: u32, 5} 6 7fn main() { 8 let book1 = Book { 9 title: String::from("Rust Programming"), 10 author: String::from("John Doe"), 11 pages: 250, 12 }; 13 14 let book2 = book1; // Transfer ownership of Book struct to book2 15 16 // println!("Title: {}", book1.title); // Causes an error because book1 no longer owns the data 17 18 let book_author = book2.author; // Transfer ownership of the "author" field ("John Doe") from book2 to book_author 19 20 // println!("Book author: {}", book2.author); // Causes error, book2 no longer owns the author field 21 22 let num_pages = book2.pages; // book2 still owns the pages field because pages is u32 (copy type) 23 println!("Number of pages: {}", book2.pages); // Prints: Number of pages: 250 24 25 26 let book_title = &book2.title; // Borrow the "title" field from book2 without transferring ownership. 27 28 println!("Book title: {}", book2.title); // Prints: Book title: Rust Programming 29 30 // let book3 = book2; // Causes error because book2 no longer owns the "author" field 31}
Let's break this code down:
- When
book1
is assigned tobook2
, ownership of theBook
struct is moved tobook2
. Therefore,book1
can no longer be used. - Attempting to access
book1.title
after this point results in a compile-time error becausebook1
no longer owns the data. - The
author
field ofbook2
is moved tobook_author
. As a result,book2
can no longer access itsauthor
field and will cause an error. - The
pages
field ofbook2
is au32
, a type that implements theCopy
trait. Therefore,num_pages
gets a copy of thepages
field, andbook2
still retains itspages
. - Borrowing the
title
field ofbook2
with&book2.title
allows access without transferring ownership. - Attempting to reassign
book2
tobook3
would cause a compile-time error becausebook2
would no longer own one of its fields (author
).
In the course, we will only use the String
data type to represent textual data in structs. Using string literals or string slices is allowed, but requires the use of lifetimes, a topic beyond the scope of this course.
Excellent work! You've now learned how to define structs in Rust, create instances, access their fields, and modify them when necessary. Structs are vital for organizing related data, making your programs more maintainable and clear.
Up next, you'll get to practice creating and manipulating structs on your own. Get ready to apply what you've learned and see the power of structs in action. Happy coding!