Project Structure
Code's organization: Learn the basics of Rust's module system to analyze a project.
Workspace and Packages
A Workspace is a feature provided by Cargo (Rust's package manager and build tool) that allows you to manage multiple related Packages within a single directory. Workspaces are optional.
By organizing packages as part of a workspace, you can share dependencies, coordinate builds, and simplify the development and testing of interconnected projects.
Workspace and packages each have their own Cargo.toml file.
Crates
A Crate is a self-contained unit of code that encapsulates a set of functionality, typically organized into modules, structs, enums, traits, and functions. This unit of code can be shared, imported, and used in other codebases.
Crates can be published to the Rust community's Crate Registry, allowing developers to include them as dependencies in their projects:
π Β The Rust communityβs Crate Registry (https://crates.io/)
Hint: Search for the keyword iota in the registry to get a list of IOTA related crates.
There are two types of crates: library crates expose public functions or items , and binary crates which are executable programs. A package can contain the source code of one or several crates.
Modules
A Module is a way to organize and group related code within a crate. It allows for logical separation and encapsulation of functionality, helping to keep code organized and maintainable.
Declaring and defining a module
There is a distinction between declaring and defining a module.
Declaring a module: Declaring a module is the process of creating a module and specifying its name and structure. It is done using the mod
keyword, followed by the module name and a block of code that defines the contents of the module. When you declare a module, you are essentially creating a namespace and organizing code within that namespace. You can declare modules in the same file or in separate files, and you can nest modules within other modules.
Defining a module: Defining a module involves implementing the functionality and providing the actual code within the declared module. It includes writing functions, structs, traits, and other items that make up the module's implementation. When you define a module, you are filling it with the necessary code and logic to perform specific tasks or provide certain functionality.
Here's an example to illustrate the difference:
// Declaration of a module named "department" (here: inline within a file)
pub mod department {
// Definition of a struct within the module
pub struct Employee {
// struct fields
}
// Definition of a function within the module
pub fn list_employees() {
// function implementation
}
}
// Usage
fn main() {
// Accessing the defined module and its items
let employee = department::Employee {};
department::list_employees();
}
Paths
A Path refers to the location of a module or item (e.g. structs, enums, functions) within the project's directory structure. It represents the hierarchical structure of directories and subdirectories.
The use
keyword in Rust is used to bring items from a module or crate into scope, allowing them to be accessed without fully qualifying their paths. It provides a way to conveniently reference items by their short names instead of using their full paths every time.
The requirement, however, is that the used modules and items are public (indicated by the keyword pub
).
In Rust, paths and namespaces are interrelated concepts that help organize and reference code elements. For example:
// Declaration of the modules (here: inline within a file)
mod company {
pub mod department {
pub fn list_employees() {
// Function implementation
}
}
}
// Usage
fn main() {
company::department::list_employees();
}
And here are some official links:
π Β Rust documentation - Managing Growing Projects with Packages, Crates, and Modules
π Β Rust documentation - Cargo Workspaces
π Β Rust reference - Visibility and Privacy
What else is good to know?
In Rust, the implementation of structs can be split into different files to improve code organization and maintainability. This allows you to separate different aspects of the struct's implementation, such as methods, associated functions, and trait implementations, into separate files.
However, it's important to note that a struct itself can only be defined in a single file. This ensures that the struct has a single, unambiguous definition within your project.
File 1: employee.rs
// employee.rs
pub struct Employee {
pub name: String,
pub age: u32,
pub position: String,
}
impl Employee {
pub fn new(name: String, age: u32, position: String) -> Self {
Employee {
name,
age,
position,
}
}
pub fn display_info(&self) {
println!("Name: {}, Age: {}, Position: {}", self.name, self.age, self.position);
}
}
File 2: employee_utils.rs
// employee_utils.rs
impl Employee {
pub fn calculate_salary(&self) -> u32 {
// Calculation logic goes here
5000
}
}