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.
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