Library crates vs. binary crates
- A library crate is a crate that other people can pull into their projects as a dependency
- We can create one using a command like
cargo new communicator --lib, where âcommunicatorâ is the name of our crate- Notice how we pass
--libinstead of--binat the end of our command
- Notice how we pass
- This creates a file
src/lib.rsinstead ofsrc/main.rsthat contains an example test
- We can create one using a command like
The module filesystem
- We can define a module in Rust with the following syntax:
mod network {
fn connect() {
}
mod client {
fn connect() {
}
}
}- Here, we define 2 modules: ânetworkâ and âclientâ
- If we want to call the âconnectâ functions in this code outside of the network module, we need to use the
::operator - Notice that even though the two functions are named the same, theyâre namespaced differently - as
network::connectandnetwork::client::connect- Thus they can have completely independent implementations and donât conflict!
- This is analogous to file systems. You can think of âclientâ as a directory within ânetworkâ (its parent directory)
- If we want to call the âconnectâ functions in this code outside of the network module, we need to use the
- Now letâs say we want to define a third module, âserverâ, but want to initialize it somewhere else to avoid cluttering this file
- Then we can declare
mod server;at the top of our file and then create a fileserver.rsin the root directory that contains our module definition- This is basically telling the compiler to look for a file
src/server.rsand reference it for all function definitions from this module - By default Rust only knows about
src/lib.rs, and we need to declare other files that we want to bring into scope
- This is basically telling the compiler to look for a file
- Be careful not to surround your code in
server.rswithmod server {- this is implicit!
- Then we can declare
- Now letâs say we have a submodule defined in
server.rswith the following contents:
mod auth {
fn login() {
}
}- And letâs say that our âloginâ function is super long and we wanted to cut down on the clutter
- Simply declaring
mod auth;and defining auth insrc/auth.rswould trigger a compiler error, because weâre declaring this in a submodule - Thus we need to move the content of
server.rsintoserver/mod.rsand then move our âloginâ function intoserver/auth.rs - Now, we can proceed with initializing
mod auth;inserver/mod.rs!
- Simply declaring
- In summary:
- If a module has no submodules, then you can put its declarations in
[name].rsfile - If a module does have submodules, then place its contents in
[name]/mod.rsand its submodule(s) in[name]/[submodule_name].rs
- If a module has no submodules, then you can put its declarations in
Controlling visibility with pub
- So, why does the compiler warn us that the function declaration
auth::loginis going unused? Isnât this technically misleading, since libraries are supposed to be used by users/other programs?- No! This is because the default state of all code is private, which means that the function
auth::loginis private and if we donât call it within our library code, no other file has rights to use it, even if itâs called in viaextern crate communicatorandcommunicator::network::server::auth::login(); - When a function is marked as public (which must be done explicitly), Rust assumes that itâll be called outside the current module and those compiler warnings go away
- No! This is because the default state of all code is private, which means that the function
- We can declare a function as public by adding the
pubkeyword before its declaration- But to make
auth::loginpublic, we need to make both âauthâ and the function âloginâ public, as follows:
- But to make
// [src/network/mod.rs]
pub mod auth;
// [src/network/auth.rs]
pub fn login() {
}- General rules for item visibility:
- If an item is public, it can be accessed through any of its parent modules
- If an item is private, it can be accessed only by its immediate parent module and any of the parentâs child modules
The use keyword, glob syntax, and super
- Letâs say we have a function nested within several modules, that we need to access like
a::series::of::nested_modules()- This can get quite lengthy, so we take advantage of Rustâs
usekeyword to bring part of the path into scope
- This can get quite lengthy, so we take advantage of Rustâs
- Letâs say for example that weâre using a bunch of functions defined in the âofâ module and donât want to specify this lengthy path over and over again; we can write the following:
use a::series::of;
fn main() {
of::nested_modules();
}- Notice that all of the children of modules arenât brought into scope - just the modules themselves (this is why we still need to specify âofâ in
of::nested_modules()) - Since enums form a namespace as well, we can do a similar thing for them!
enum TrafficLight {
Red,
Yellow,
Green,
}
use TrafficLight::{Red, Yellow};
fn main() {
let red = Red;
let yellow = Yellow;
let green = TrafficLight::Green;
}- If we want to pull in all items in a namespace at once, we can use the
*syntax, which is called the glob operator. Below is a refactoring of the code we just wrote using this syntax:
use TrafficLight::*;
fn main() {
let green = Green;
}- Use globs sparingly, because they usually bring more than what you need into context and sometimes cause naming conflicts
- Letâs take the following code snippet:
pub mod client;
#[cfg(test)]
mod tests {
fn call_connect() {
client::connect();
}
}- Our call within
call_connect()would result in a compiler error, because technicallyclient::connectbelongs to the root (or in this case, parent) module and we are within a submoduletestwhen weâre calling it - There are two ways to fix this:
::client::connect(), where the::prefix means that we need to specify the full-length path from root- Usually not preferred because itâs quite lengthy in most cases
super::client::connect(), where thesuper::prefix moves us up one level and allows us to call any sibling modules (whichclientqualifies as)- Itâs annoying to type
super::before every declaration, so we usually just include something likeuse super::client;at the top of our module
- Itâs annoying to type
References
- Chapter 7 of The Rust Programming Language by Steve Nichols and Nicole Klabnick.