Hello! In this lesson, we will explore an essential aspect of Rust programming and OOP — modules and encapsulation. This lesson welcomes you into the world of Rust's modularity and the principles of encapsulation, key features in writing maintainable and scalable code.
Modules in Rust help organize code into separate namespaces, making it easier to manage and navigate larger projects. Encapsulation allows you to restrict access to parts of your code, promoting safer and more intentional interactions with your data structures.
In this lesson, we will:
- Introduce the concept and syntax of modules
- Learn to control visibility with
pub
- Implement encapsulated data using
struct
methods
Let's dive in!
Modules in Rust are like containers that organize your functions, structs
, traits, and methods. They allow you to create structs and methods while using encapsulation to control access to a structs methods and fields.
Here's a simple example to create a module in Rust:
Rust1mod bank { 2 struct BankAccount { 3 balance: f32, 4 name: String, 5 } 6}
In this example:
- We defined a module named
bank
using themod
keyword - Inside the
bank
module, we created aBankAccount
struct with two fields
Now that we have created a module, let's explore how to use it! By default, any structs or methods defined in a module are private and cannot be accessed by code outside the module. To allow access to code inside the module, we use the pub
keyword, making the code public. In this section we make the BankAccount
public, create a public constructor, and create a new BankAccount
instance.
Rust1mod bank { 2 pub struct BankAccount { 3 balance: f32, 4 name: String, 5 } 6 7 impl BankAccount { 8 pub fn new(balance: f32, name: String) -> BankAccount { 9 BankAccount { balance: balance, name: name } 10 } 11 } 12} 13 14fn main() { 15 let my_account = bank::BankAccount::new(1000.0, String::from("Cosmo")); 16}
- Adding the
pub
keyword to theBankAccount
struct allows code in themain
function to access thestruct
- We added a public associated function for the
BankAccount
struct callednew
that creates a new instance of aBankAccount
- Inside
main
, we created an instance of aBankAccount
- The syntax to access the
new
method is<module name>::<struct name>::<struct method>(<input parameters>)
Let's take a deeper dive into encapsulation with the pub
keyword. Continuing with the BankAccount
example, currently both fields of the struct are private.
We want users to be allowed to check the balance and deposit money, but not allow them to directly access the balance
field. To do this, we will create a public deposit
method to change the balance
field and a public get_balance
method.
Additionally, we want the name associated with the account to be accessed by anyone, so we make it public. Let's take a look:
Rust1mod bank { 2 pub struct BankAccount { 3 balance: f32, 4 pub name: String, 5 } 6 7 impl BankAccount { 8 pub fn new(balance: f32, name: String) -> BankAccount { 9 BankAccount { balance: balance, name: name } 10 } 11 12 pub fn deposit(&mut self, amount: f32) { 13 self.balance += amount; 14 } 15 16 pub fn get_balance(&self) -> f32 { 17 self.balance 18 } 19 } 20} 21 22fn main() { 23 let mut my_account = bank::BankAccount::new(1000.0, String::from("Cosmo")); 24 25 my_account.deposit(500.0); 26 println!("Balance: ${}", my_account.get_balance()); // Prints: Balance: $1500 27 //println!("Account balance is {}", my_account.balance); // Causes error because balance field is private 28 29 println!("Account belongs to {}", my_account.name); // Prints: Account belongs to Cosmo 30}
Let's break this code down step by step.
- Inside the
bank
module, we've changed thename
field to be public - We've created a public method
deposit
that takes in a mutable reference toself
- We've created a public method
get_balance
that returns the account balance.
In the main function
- We create a new instance of
BankAccount
called my_account - The
deposit
andget_balance
methods are public, so we can call them and print the results. - When we try to access
my_account.balance
, we will get an error because thebalance
field is private - We can directly access the
name
field because it is public
To make the importance of encapsulation more clear we will allow users to withdraw money, ensuring the account balance does not become negative.
Adding onto the code from the previous section:
Rust1mod bank { 2 pub struct BankAccount { 3 balance: f32, 4 pub name: String, 5 } 6 7 impl BankAccount { 8 pub fn new(balance: f32, name: String) -> BankAccount { 9 BankAccount { balance: balance, name: name } 10 } 11 12 pub fn deposit(&mut self, amount: f32) { 13 self.balance += amount; 14 } 15 16 pub fn get_balance(&self) -> f32 { 17 self.balance 18 } 19 20 pub fn withdraw(&mut self, amount: f32) -> String { 21 if self.balance >= amount { 22 self.balance -= amount; 23 String::from("Account balance: $") + &self.balance.to_string() 24 } else { 25 String::from("Insufficient Funds") 26 } 27 } 28 } 29} 30 31fn main() { 32 let mut my_account = bank::BankAccount::new(1000.0, String::from("Cosmo")); 33 34 println!("{}", my_account.withdraw(300.5)); // Prints: Account balance: $1199.5 35 println!("{}", my_account.withdraw(2000.0)); // Prints: Insufficient Funds 36 37 // my_account.balance -= 2000.0 // Causes error since `balance` is private 38 39 println!("Final balance: ${}", my_account.get_balance()); // Prints: Final balance: $1199.5 40}
The withdraw
method ensures that amount
can be withdrawn from the balance
without the account becoming negative.
The code shows the successful withdrawal of $300.50 and an unsuccessful withdrawal of $2000.
The attempt to directly modify my_account.balance
causes an error because the balance
field is private. If users could directly modify the balance
field, they could withdraw more money than they actually have.
Great job! You've learned how to create and use modules, control access with pub
, and apply encapsulation in Rust. These skills are crucial for organizing your code and enhancing its readability, safety, and maintainability.
Now it’s time to practice what you’ve learned. Get ready to dive into the exercises and reinforce your understanding of modules and encapsulation in Rust.