Skip to content

Package Management

Package management comes into play when a project gets bigger and bigger. Dividing codes into multiple files and grouping them make a project more maintainable.

A package can contain multiple binary crate and optionally one library crate.

Additionally, a package can help encapsulate codes that are not meant for others to see. It can also enable scoping.

Rust's Module System:

  • Packages: Cargo feature that let user build, test and share crates
  • Crates: A tree of modules that produces a library or executable
  • Modules and use: Let user contron the organization, scope and encapsulation.
  • Paths: A way of naming an item, such as struct, function or module

Understanding cargo new project-name

When user runs cargo new project-name, Cargo generates a couple of files inside project-name directory. They are: config.toml and src/main.rs.

config.toml is the configuration file. This file contains dependancies and other project related informations.

src/main.rs is the entry point for the Rust compiler. One does not have to mention entry point in config file, as Rust treats main.rs as entry point by convention. Similarly, src/lib.rs means the project contains a library crate with the same name as package. There can be any number of binaries inside bin/ directory.

The mod

mod token stands for module. It can be used to declare a module. A module can host multiple modules nested inside it. Other than modules, it can hold struct, functions etc.

An example:

mod front_of_the_house {
    mod hosting {
        fn add_to_wishlist() {};
    }
}

Paths

Rust find the codes we want to use by path. A path can be relative or absolute. Absolute path starts with crate name or literal crate.

A relative path, on the other hand, starts from the current module and uses self, super or an indetifier in current module.

Both absolute and relative paths are followed by one or more indetifier separated by ::.

Example:

pub fn call_them() {
    // absolute path
    crate::front_of_the_house::hosting::add_to_wishlist();

    // relative path
    front_of_the_house::hosting::add_to_wishlist();
}

Exposing Paths using pub

By default, a path is always private. In order to make it public, pub keyword must be prepend.

Example:

mod front_of_the_house {
    mod hosting {
        fn add_to_wishlist() {};
    }
}

Relative path using super

super keyword is similar to UNIX .. operator. It refers to parent module.

Making enum and struct public

Prepeding pub keyword to a struct or enum would make it public. However, an extra step would require for making struct field public. One need to prepend pub keyword to any field that are supposed to be public.

Example:

mod back_of_house {
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String,
    }

    impl Breakfast {
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast: String::from(toast),
                seasonal_fruit: String::from("peaches"),
            }
        }
    }
}

pub fn eat_at_restaurant() {
    let mut meal = back_of_house::Breakfast::summer("Rye");

    meal.toast = String::from("Wheat");
    println!("I'd like {} toast please", meal.toast);
}

The use Keyword

use keyword brings function, struct etc into the scope where it is being called from. Example:

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use front_of_the_house::hosting;

hosting::add_to_waitlist();   // this will refer to module `front_of_the_house`

It is wise to call parent(or a location) which clearly explains where the said variable is defined, instead of just the associated function.

use front_of_the_house::hosting::add_to_waitlist; // not good
use front_of_the_house::hosting; // good. now we know where it is defined

Apart from that, bringing same two or more types with same name into scope requires to call their parent modules.

use std::fmt;
use std::io;

fn function1() -> fmt::Result {}

fn function2() -> io::IoResult<()> {}

The as keyword

as keyword can rename a module so that things does not conflict with each other.

use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {}

fn function2() -> IoResult<()> {}

Re-exporting an imported name

When bringing in a name into scope using use, the name is private to current scope. This name could be re-exported for external codes to call it.

pub use crate::front_of_the_house::hosting;

In above example, anything that is attached to hosting that is public, would become available for external codes to call.

Using external packages

In order to use an external crate from crates.io, one needs to define it in Cargo.toml file. When building the project, cargo automatically downloads all mentioned crates from Cargo.toml and adds them to project.

// Cargo.toml
[dependencies]
rand = "0.5.5"

Calling rand in code:

use rand::Rng;

fn main() {
  let number = rand::thread_rng().gen_range(1, 101);
}

Using nested paths

Different varient of use keyword can be used to reduce use list.

// SAME PREFIX

// this ...
use std::cmp::Ordering;
use std::io;

// can be written as ...
use std::{cmp::Ordering, io};



// ELIMINATE COMMON PART

use std::io;
use std::io::Write;
// to
use std::io::{self, Write};

Glob Operator (*)

Glob operator bring every public names into scope. Example: use std::collections::*;