Rust: From 0 To Publish

As software projects grow in size and/or time, organization and documentation of code becomes increasingly important. We humans tend to forget things and keeping track of an entire program structure naturally becomes more and more difficult as the project evolves.

By grouping related functionality and separating code with distinct features, it is easier to find particular features and where to change how something works. Additionally, especially if other developers are also supposed to work with your code and/or modify it, good documentation is just as important.

To do all of this in a standardized and consistent way in Rust, there is Cargo.

Cargo is the Rust package manager which downloads your package’s dependencies, compiles your packages, builds documentation, makes distributable packages, and uploads them to crates.io, the Rust community’s package registry.

Installing Rust

Starting from 0 means installing the Rust programming language in the first place. Fortunately, it couldn't be easier:

Unix-like (Linux, MacOS)

On Unix, run

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 

in your shell. This downloads and runs rustup-init.sh, which in turn downloads and runs the correct version of the rustup-init executable for your platform.

Windows

On Windows, download and run rustup-init.exe.

Updating Rust

rustup update

Cargo Overview

Cargo does (almost) everything

Cargo makes building, testing, documenting, running and installing rust applications very easy and standardized.

Building, Running and Installing

Compile a local package and all of its dependencies with cargo build.

cargo build [--release]

--release

By default, cargo runs everything in debug mode.

Scopes preceded with #[cfg(debug_assertions) will only be used when not using this flag.

Scopes preceded with #[cfg(not(debug_assertions))] will only be used when using this flag.

--example

By default, cargo builds and runs ./src/lib.rs and/or ./src/main.rs but you can use different implementations by passing an executable from ./examples/.

cargo build --example EXAMPLE.rs [--release]

will build ./src/lib.rs and/or ./src/main.rs.

or both

cargo build --example example1 --release

Replace build with run to also launch the executable.

Replace build with install to install the binary system-wide. On Linux, the default location is $HOME/.cargo/bin/.

Testing and Documenting

Execute all unit and integration tests and build examples of a local package

cargo test

Cargo uses rustdoc (TODO insert link) to build documentation for your crate. Create documentation with

cargo doc

User Guides with mdBook

TODO: Building user guides with mdBook...

Creating A New Project

Complete Rust Crate Boilerplate

Rhymes included.

Github

A Crate From Scratch

From your favorite directory, create a new crate with:

$ cargo new rust-boilerplate
   Created binary (application) `rust-boilerplate` package

This creates some basic package boilerplate and also initializes a new git repository. Neat!

$ tree rust-boilerplate/ -a
rust-boilerplate/
├── Cargo.toml
├── .git
│   ├── ...
├── .gitignore
└── src
    └── main.rs
$ cat rust-boilerplate/src/main.rs 
fn main() {
    println!("Hello, world!");
}

Now you should be able to run the new crate's simple executable from the project's root directory.

$ cargo run
   Compiling rust-boilerplate v0.1.0 (/home/chris/Software/Rust/rust-boilerplate)
    Finished dev [unoptimized + debuginfo] target(s) in 0.97s
     Running `target/debug/rust-boilerplate`
Hello, world!

More Basic Boilerplate

Cargo creates a very basic project with only the bare necessities to run an executable rust application. However, most projects require a lot more "infrastracture". Let's provide some more basic stuff that you will generally need.

Adding A Library

Create src/lib.rs and put


#![allow(unused)]
fn main() {
pub fn hello() {
    println!("Hello, world!");
}
}

inside.

Now use this library in the crate's entrypoint by changing main.rs to

extern crate rust_boilerplate as bp;


fn main() {
    bp::hello();
}

Adding A Module (Or Two)

chris@XPS15:~/Software/Rust/rust-boilerplate$ tree .
.
├── Cargo.lock
├── Cargo.toml
├── README.md
├── src
│   ├── config2
│   │   ├── mod.rs
│   │   └── subconfig.rs
│   ├── config.rs
│   ├── lib.rs
│   └── main.rs

Simple Modules VS Nested Module


#![allow(unused)]
fn main() {
mod subconfig;

pub struct Config2 {
    pub greeting: String,
}


impl Config2 {
    pub fn default() -> Self {
        Config2 {
            greeting: String::from("Hello, world!"),
        }
    }

    pub fn print(&self) {
        println!("Config2.greeting = {}", self.greeting);
        subconfig::subconfig_func();
    }
}
}

#![allow(unused)]
fn main() {
pub fn subconfig_func() {
    println!("called subconfig_func");
}
}

#![allow(unused)]
fn main() {
pub struct Config {
    pub greeting: String,
}


impl Config {
    pub fn default() -> Self {
        Config {
            greeting: String::from("Hello, world!"),
        }
    }

    pub fn print(&self) {
        println!("Config.greeing = {}", self.greeting);
    }
}
}

Above is essentially the same thing, stick with "simple" config here and create utils package

Creating A Utils Module

TODO

Testing

Adding A Unit Test To A Module

TODO

Adding An Integration Test To A Crate

TODO

Logging

Documentation with rustdoc

Guides with mdBook

Publishing