Stránky

Tuesday, May 31, 2016

Programming in Rust - Part 1:Modules and Libraries

After a few years of Java development it's time to look for interesting features new languages can offer. Choice came down to Go, Erlang/Elixir and Rust. I'll talk about how I came to these three in another post. For now it is important to note that my personal favorite is Rust.

Tutorial will be written as a diary while developing simple and/or trivial projects. I'll not cover Rust syntax. Rust programming language has really excellent tutorial at Rust book.

Modules vs Libraries


First lesson is to understand how Rust splits code into logical parts. Rust has source files (*.rs), modules and libraries - known as crates in rust terminology. I have come into confusion over Rust keywords related to usage of libraries and modules.

First create basic Rust project. We will use Cargo a build system which comes as part of a Rust install package.

Dedicate some directory to your Rust projects and within this directory create new Rust library project.

> cargo new mylib
> cd mylib/src

Inside created project you'll find src directory with lib.rs file. Cargo uses lib.rs as root module for library project, and main.rs for executable project. We'll create new executable root main.rs and two module files. This way we can experiment with modules and libraries within one project.

> touch main.rs
> touch moda.rs
> touch modb.rs
> cd ..
Insert following content into main.rs.

// file: src/main.rs
fn main() {
    println!("Hello World!");
}
Run application by:

> cargo run
You should see "Hello World!" printed in console. When working with libraries and modules you will find three keywords referenced all over rust source code.

extern
mod
use
Just to reduce confusion right at the beginning let's forget 'use' keyword as it is just marginally related to modules and libraries and does not directly relate to our topic. Rust can compile against source code or against crates(libraries). Important to understand is that 'mod' keyword is used when we compile against source code and 'extern' is keyword used when we compile against libraries. Let's explain this by some examples. Open main.rs and modify content as follows.

// file: src/main.rs
mod modc{
    pub fn hello() {
        println!("modc hello.");
    }
}

fn main() {
    println!("Hello World!");
    modc::hello();
}
We have created module modc within main.rs. source code file. Since it is defined within same file it is quite easy to use within main function.


Compile against source code


Open file moda.rs and modify content to:

// file: src/moda.rs
pub fn hello() {
    println!("moda hello.");
}

Next modify main.rs

// file: src/main.rs
mod modc {
    pub fn hello() {
        println!("modc hello.");
    }
}

mod moda;

fn main() {
    println!("Hello World!");
    modc::hello();
    moda::hello();
}
Common source of confusion is why we need to declare module moda. Without:

mod moda;
Rust would not be able to compile main.rs.

mod <modname>;
looks for file <modname>.rs or <modname>/mod.rs for inclusion. If it finds one, it'll create namespace within requesting file scope. In our case main.rs. Namespace will be modname. In our case Rust looks for moda.rs file, it finds one, takes content of the file and within main.rs creates namespace moda. From this time we can reference function from other module defined in other file by:

moda::hello();

Compile against crate - library


Now we'll use crate for our third module definition. Within our lib.rs file we'll reference another module. Crate will be compiled and linked to main.rs executable.

In modb.rs insert:

// file: src/modb.rs
pub fn hello() {
    println!("modb hello.");
}
Next modify lib.rs

// file: src/lib.rs
pub mod modb;
And finally update main.rs

// file: src/main.rs
mod modc {
    pub fn hello() {
        println!("modc hello.");
    }
}

mod moda;

extern crate mylib;

fn main() {
    println!("Hello World!");
    modc::hello();
    moda::hello();
    mylib::modb::hello();
}
Again run code by 'cargo run' command. As first step cargo compiled lib.rs. This file represents crate which will be used to build our executable. Crate is compiled against file modb.rs which was expanded within lib.rs file scope, creating modb namespace.

Then cargo compiled main.rs. It has found reference to crate mylib in:

extern crate <cratename>;
Rust created namespace <cratename> within main.rs and linked crate to executable. From this time we are able to use library function within our code. Last example will define module directly within our root crate file lib.rs.

Update lib.rs to contain

// file: src/lib.rs
pub mod modb;

pub mod modd {
    pub fn hello() {
        println!("modd hello.");
    }
}
and main.rs

// file: src/main.rs
mod modc {
    pub fn hello() {
        println!("modc hello.");
    }
}

mod moda;

extern crate mylib;

fn main() {
    println!("Hello World!");
    modc::hello();
    moda::hello();
    mylib::modb::hello();
    mylib::modd::hello();
}
After previous explanation last example should be self explaining.

Edit:
One of the blog readers pointed out not to forget to mention where does the crate name mylib comes from. Valid point.
Cargo keeps its build configuration in Cargo.toml file placed in root of the project.

//Cargo.toml
[package]
name = "mylib"
version = "0.1.0"
authors = ["Robert Gallas"]

[dependencies]
Here cargo reads name of the crate and instructs rust compiler with configured crate name. Library client then use this name, when referencing crate in source code.

'use' keyword


It's time to explain why we use 'use' keyword. 'use' is used to shorten fully qualified names so developers can avoid use of functions and structures prefixed with whole list of module names. Another nice feature missing in Java but present in Rust and another languages is aliasing of 'use'd modules. Following example should be self explaining.

// file: src/main.rs
mod modc {
    pub fn hello() {
        println!("modc hello.");
    }
}

mod moda;

extern crate mylib;

use mylib::modb;
use mylib::modd::hello as hellod;

fn main() {
    println!("Hello World!");
    modc::hello();
    moda::hello();
    mylib::modb::hello();
    mylib::modd::hello();
    modb::hello();
    hellod();
}
Hope article saves you few hours when learning how to use Rust modules and libraries.

1 comment:

  1. "After a few years of Java development it's time to look for interesting features new languages can offer. Choice came down to Go, Erlang/Elixir and Rust. I'll talk about how I came to these three in another post. For now it is important to note that my personal favorite is Rust.": all of these is about my way too.)

    ReplyDelete