“Do the best you can until you know better. Then when you know better, do better.”
__ Maya Angelou


In the previous posts, we discussed more about the theory parts of Rust programming language.

▸ Installation & Hello World ▸ Cargo & Crates ▸ Variable bindings , Constants & Statics ▸ Comments ▸ Functions ▸ Primitive Data Types ▸ Operators 
▸ Control Flows

▸ Vectors ▸ Structs ▸ Enums ▸ Generics ▸ Impls & Traits

▸ Ownership ▸ Borrowing ▸ Lifetimes & Lifetime Elision

But today we are going to discuss more about the the practical stuff of writing Rust programs.

Code Organization

When a single code block is getting larger, it should be decomposed into smaller pieces and should be organized in a proper manner. Rust supports different levels of code organization.

  • Functions
  • Modules
    Can be mapped to a
     ▸ Inline module
     ▸ File 
     ▸ Directory hierarchy
  • Crates
    Can be mapped to a 
     ▸ lib.rs file on the same executable crate
     ▸ Dependency crate specified on Cargo.toml
    - Dependency can be specified from
     ▸ path
     ▸ git repository
     ▸ crates.io
  • Workspaces
    Helps to manage multiple crates as a single project

Let’s discuss one by one.

💡 To make examples more simpler, we use a simple function which prints “Hello, world!”. But regarding writing testable codes, always try to return the String from the function and print it when calling it, instead printing the String inside the function.

01. Functions

Functions are the first line of organization in any program.

We can add unit tests in the same file.

💭 An attribute is a general, free-form metadatum that is interpreted according to name, convention, and language and compiler version.

02. Modules

Inline modules/ Modules in the same file
Related code and data are grouped into a module and stored in the same file.

Modules can also be nested.

Private functions can be called from the same module or from a child module.

💡 self keyword is used to refer same module, while super keyword is used to refer parent module. Also super keyword can be used to access root functions from inside a module.
🔎 When writing tests it’s a good practice to write tests inside a test module because of they compile only when running tests.

Modules - In a different file in the same directory

If we wrap file content with a mod declaration, it will act as a nested module.

Modules - In a different file in a different directory
mod.rs in the directory module root is the entry point to the directory module. All other files in that directory root, act as sub-modules of the directory module.

Again, If we wrap file content with a mod declaration, it will act as a nested module.

Other files in the directory module act as sub-modules for mod.rs

⭐️ If you need to access an element of phrases/greetings.rs from outside the module, you have to import greetings module as a public module.

🔎 It’s unable to import child file modules of directory modules to main.rs, so you can’t use mod phrases::greetings; from main.rs. But there is a way to import phrases::greetings::hello() to phrases module by re-exporting hello to phrases module. So you can call it directly as phrases::hello().

This allows you to present an external interface that may not directly map to your internal code organization. If it is still not clear, don’t worry; We discuss the usages of use on an upcoming section in this post.

03. Crates

💭 Crates are bit similar to the packages in some other languages. Crates compile individually. If the crate has child file modules, those files will get merged with the crate file and compile as a single unit.
💭 A crate can produce an executable/ a binary or a library. src/main.rs is the crate root/ entry point for a binary crate and src/lib.rs is the entry point for a library crate.

lib.rs file on the same executable crate
💡 When writing binary crates, we can move the main functionalities to src/lib.rs and use it as a library from src/main.rs . This pattern is quite common on executable crates.

💯 As I mentioned earlier, in here we use simplest examples to reduce the complexity of learning materials. But this is how we need to write greetings/src/lib.rs to make the code more testable.
📖 When importing a crate that has dashes in its name “like-this”, which is not a valid Rust identifier, it will be converted by changing the dashes into underscores, so you would write extern crate like_this;

lib.rs can link with multiple files.

▸ Dependency crate specified on Cargo.toml
When the code in the lib.rs file is getting larger, we can move those into a separate library crate and use it as a dependency of the main crate. As we mentioned previously, a dependency can be specified from a folder path, git repository or by crates.io.

» Let’s see how to create a nested crate and use it as a dependency using folder path,

» If you want to use a library crate on multiple projects, one way is moving crate code into a git repository and use it as a dependency when required.

» The other way is uploading it to crates.io and use it as a dependency when needed.

🚧 First, let’s create a simple “Hello world” crate and upload it to crates.io .

💭 //! doc comments are used to write crate and module-level documentation. In other places we have to use /// outside of the block. And when uploading a crate to crates.io, cargo generates the documentation from these doc comments and host it on docs.rs.

💡 We have to add the description and license fields to Cargo.toml, otherwise we will get error: api errors: missing or empty metadata fields: description, license. Please see http://doc.crates.io/manifest.html

To upload this to crates.io, 
01. We have to create an account on crates.io to acquire an API token
02. Then run cargo login <token> with that API token and cargo publish

📖 This is how it describes on Cargo Docs with more details.

- You’ll need an account on crates.io to acquire an API token. To do so, visit the home page and log in via a GitHub account (required for now). After this, visit your Account Settings page and run the cargo login command specified. 
Ex. cargo login abcdefghijklmnopqrstuvwxyz012345
- The next step is to package up your crate into a format that can be uploaded to crates.io. For this we’ll use the cargo package sub-command.
- Now, it can be uploaded to crates.io with the cargo publish command.
- If you’d like to skip the cargo package step, the cargo publish subcommand will automatically package up the local crate if a copy isn’t found already.

The name of our crate is test_crate_hello_world. So it can be found on,
 📦 https://crates.io/crates/test_crate_hello_world
 📑 https://docs.rs/test_crate_hello_world

💯 crates.io supports readme files as well. To enable it, we have to add the readme field to Cargo.toml. Ex: readme="README.md"

🏗️ Okay then, Let’s see how we can use this from an another crate.

By default, Cargo checks dependencies on crates.io . So we have to add only the crate name and a version string to Cargo.toml and then run cargo build to fetch the dependencies and compile them.

04. Workspaces

When the code base is getting larger, you might need to work on multiple crates on the same project. Rust supports this via Workspaces. You can build, run tests or generate docs for all crates at once by running cargo commands from the project root.

⭐️ When working on multiple crates at the same time, there is a higher possibility to having shared dependencies on crates. To prevent downloading and compiling the same dependency multiple times, Rust uses a shared build directory under the project root, while running cargo build from the project root.

🔎 rust-lang/rust source folder is a good example for a workspace.


▸ Mainly use keyword is used to bind a full path of an element to a new name. So user doesn’t want to repeat the full path each time.

▸ Another common usage of use is importing elements to scope. Remember that, this is also bit similar to creating alias and using it instead of using the full path.

💡 By default, use declarations use absolute paths, starting from the crate root. But self and super declarations make that path relative to the current module.

Same way use keyword is used to import the elements of other crates including std , Rust’s Standard Library.

We don’t need to use extern crate std; when using std library. We will discuss more about this under Standard Library section.

💡 use statements import only what we’ve specified into the scope, instead of importing all elements of a module or crate. So it improves the efficiency of the program.

▸ Another special case is pub use. When creating a module, you can export things from another module into your module. So after that they can be accessed directly from your module. This is called re-exporting.

This pattern is quite common in large libraries. It helps to hide the complexity of the internal module structure of the library from users, because users don’t need to know/follow whole directory map of the elements of the library while working with them.

Rust Standard Library, Primitives and Preludes

⭐️ In Rust, language elements are implemented by not only std library crate but also compiler as well. Examples,

  • Primitives : Defined by the compiler and methods are implemented by std library directly on primitives.
  • Standard Macros : Defined by both compiler and std

std library has been has divided into modules, according to the main areas each covered.

⭐️ While primitives are implemented by the compiler, the standard library implements most useful methods directly on the primitive types. But some rarely useful language elements of some primitives are stored on relevant std modules. This is why you can see char, str and integer types on both primitives and std modules.
🔎 When examine Rust’s source code, you can see that src directory is a workspace. Even though its having many library crates, by examine root Cargo.toml file you can easily identify that main crates are rustc(compiler) and libstd (std). In libstd/lib.rs std modules have been re-exported via pub use and the original location of most of std modules is src/libcore .

Few important std modules are,
 » std::io - Core I/O functionality 
 » std::fs - Filesystem specific functionality
 » std::path - Cross platform path specific functionality
 » std::env - Process’s environment related functionality
 » std::mem - Memory related functionality
 » std::net - TCP/UDP communication
 » std::os - OS specific functionality
 » std::thread - Native threads specific functionality
 » std::collections - Core Collection types

💯 Refer Rust Standard Library Documentation for more details.

⭐️ Even though Rust std contains many modules, by default it doesn’t load each and every thing of std library on every rust program. Instead, it loads only the smallest list of things which require for almost every single Rust program. These are called preludes. They import only,

Preludes have been imported explicitly on libstd/lib.rs and the whole list can be seen on libstd/prelude/v1.rs .

⭐️ So technically, Rust inserts,
 ▸ extern crate std; : into the crate root of every crate
 ▸ use std::prelude::v1::*; : into every module
So you don’t need to import these each time.

The concept of preludes is quite common on Rust libraries. Even some modules inside std crate (ex.libstd/io) and many libraries (ex. diesel) are having their own prelude modules.

⭐️ But preludes are used to create a single place to import all important components which are required while using the library. They do not load automatically unless you imported them manually. Only std::prelude imports automatically in every Rust programs.

🚧 An example about how we can organize code while using structs and traits inside modules will be added after few days.

Ok, Let’s stop the fourth post of Learning Rust (& Gitbook)series in here. In this post I just tried to summarize about,

▸ Modules ▸ Crates ▸ Workspaces ▸ std modules and Preludes

🐣 I am a 🇱🇰 Web Developer who lives in 🇻🇳. So I am not a native English speaker and just learning Rust, If you found any mistake or something need to be changed, even a spelling or a grammar mistake, please let me know. Thanks.

Okay, see you on the next post :)

“It is never too late to be what you might have been.”
__ George Eliot
Like what you read? Give Dumindu Madunuwan a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.