Rust Introduction

Junfeng Liu

2017-06-02

The Rust Programming Language

Rust is a systems programming language focused on three goals: safety, speed, and concurrency.

Rust combines low-level control over performance with high-level convenience and safety guarantees.

Rust is a language for confident, productive systems programming.

Why Rust

  • Open Source and Open Governance
  • Open Source and Open Governance
  • Top-tier performance   (like C/C++ or better)​​​​
  • Memory safe   (No memory leak)
  • No runtime, no GC   (runs everywhere)
  • No undefined behavior
  • Zero-cost abstractions
  • Ergonomic syntax
  • Expressive data structures
  • Targets the same use cases as C/C++   (all of them)
  • Sponsored by Mozilla  (makers of Firefox)
  • Most loved programming language ( Stack Overflow Developer Survey in 2016 and 2017)

Rust History

  • Open Source and Open Governance
  • Started in 2006 by Mozilla employee Graydon Hoare.
  • Mozilla began sponsoring the project in 2009 and announced it in 2010.
  • In 2010 shifted from the initial compiler (written in OCaml) to the self-hosting compiler written in Rust and successfully compiled itself in 2011.
  • 2012-01-20 first pre-alpha release 0.1
  • 2015-05-15 first stable release 1.0
  • Buffer overflow
  • Stack overflow
  • Use after free
  • Double free
  • Data races
  • Segmentation fault

MEMORY SAFETY ERRORS

RUST'S KEY DESIGN PROBLEM

How can I maintain memory safety in a concurrent program without a global GC?

OWNERSHIP AND BORROWING

In Rust, every value has a single, statically-known, owning path in the code, at any time.


Pointers to values have limited duration, known as a "lifetime", that is also statically tracked.


All pointers to all values are known statically.

OWNERSHIP AND BORROWING OPERATIONS

  • Move an owned value

  • Borrow a shared reference (&T)

  • Borrow a mutable reference (&mut T)

Immutable by default

let x = 0;
let mut y = 1;

helloworld.rs

fn main() {
  println!("Hello, World!");
}

INSTALLATION

  • Arch Linux: pacman -S rustup
  • Unix: curl https://sh.rustup.rs -sSf | sh
  • Windows: download and run the rustup-init.exe
rustup install nightly
rustup default nightly
rustup update nightly
rustup self update
rustup target list
rustup target add x86_64-unknown-linux-musl
rustup component add rls

BUILD

cargo new <project> --bin
cargo run
cargo run --example json
cargo build
cargo build --release --target x86_64-unknown-linux-musl
cargo update
cargo doc --open
cargo test
cargo bench
cargo login
cargo publish

Discover and download packages

Parse command line arguments with clap:

extern crate lopdf;
use lopdf::Document;

#[macro_use]
extern crate clap;
use clap::{App, Arg, SubCommand};

use std::str::FromStr;

fn main() {
    let app = App::new("PDF utility program using lopdf library")
        .version(crate_version!())
        .author(crate_authors!())
        .arg(Arg::with_name("input")
            .short("i")
            .long("input")
            .value_name("input file")
            .takes_value(true)
            .global(true))
        .arg(Arg::with_name("output")
            .short("o")
            .long("output")
            .value_name("output file")
            .takes_value(true)
            .global(true))
        .subcommand(SubCommand::with_name("process")
            .about("Process PDF document with specified operations")
            .arg(Arg::with_name("operations")
                .value_name("operations")
                .help("e.g. prune_objects delete_zero_length_streams renumber_objects")
                .takes_value(true)
                .multiple(true)))
        .subcommand(SubCommand::with_name("compress")
            .about("Compress PDF document"))
        .subcommand(SubCommand::with_name("decompress")
            .about("Decompress PDF document"))
        .subcommand(SubCommand::with_name("delete_pages")
            .about("Delete pages")
            .arg(Arg::with_name("pages")
                .value_name("page numbers")
                .help("e.g. 3,5,7-9")
                .takes_value(true)))
        .subcommand(SubCommand::with_name("prune_objects")
            .about("Prune unused objects"))
        .subcommand(SubCommand::with_name("delete_objects")
            .about("Delete objects")
            .arg(Arg::with_name("ids")
                .value_name("object ids")
                .help("e.g. \"1 0,2 1,35,36\"")
                .takes_value(true)))
        .subcommand(SubCommand::with_name("renumber_objects")
            .about("Renumber objects"))
        .subcommand(SubCommand::with_name("delete_zero_length_streams")
            .about("Delete zero length stream objects"))
        .get_matches();

    if let (cmd, Some(args)) = app.subcommand() {
        if let Some(input) = args.value_of("input") {

            println!("Open {}", input);
            let mut doc = Document::load(input).unwrap();

            println!("Do {}", cmd);
            match cmd {
                "process" => {
                    if let Some(operations) = args.values_of("operations") {
                        for operation in operations {
                            println!("Do {}", operation);
                            apply_operation(&mut doc, operation);
                        }
                    }
                }
                "delete_pages" => {
                    if let Some(pages) = args.value_of("pages") {
                        let mut page_numbers = vec![];
                        for page in pages.split(',') {
                            let nums: Vec<u32> = page.split('-').map(|num|u32::from_str(num).unwrap()).collect();
                            match nums.len() {
                                1 => page_numbers.push(nums[0]),
                                2 => page_numbers.append(&mut (nums[0]..nums[1]+1).collect()),
                                _ => {}
                            }
                        }
                        doc.delete_pages(&page_numbers);
                    }
                }
                "delete_objects" => {
                    if let Some(ids) = args.value_of("ids") {
                        for id in ids.split(',') {
                            let nums: Vec<u32> = id.split(' ').map(|num|u32::from_str(num).unwrap()).collect();
                            match nums.len() {
                                1 => doc.delete_object(&(nums[0], 0)),
                                2 => doc.delete_object(&(nums[0], nums[1] as u16)),
                                _ => None
                            };
                        }
                    }
                }
                operation @ _ => {
                    apply_operation(&mut doc, operation);
                }
            }

            doc.change_producer("https://crates.io/crates/lopdf");

            if let Some(output) = args.value_of("output") {
                println!("Save to {}", output);
                doc.save(output).unwrap();
            }
        }
    }

    fn apply_operation(doc: &mut Document, operation: &str) {
        match operation {
            "compress" => doc.compress(),
            "decompress" => doc.decompress(),
            "renumber_objects" => doc.renumber_objects(),
            "prune_objects" => {
                let ids = doc.prune_objects();
                println!("Deleted {:?}", ids);
            }
            "delete_zero_length_streams" => {
                let streams = doc.delete_zero_length_streams();
                if streams.len() > 0 {
                    println!("Deleted {:?}", streams);
                }
            }
            _ => {}
        }
    }
}
> ./pdfutil help

PDF utility program using lopdf library 0.1.0
Junfeng Liu <china.liujunfeng@gmail.com>

USAGE:
    pdfutil [OPTIONS] [SUBCOMMAND]

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

OPTIONS:
    -i, --input <input file>
    -o, --output <output file>

SUBCOMMANDS:
    compress                      Compress PDF document
    decompress                    Decompress PDF document
    delete_objects                Delete objects
    delete_pages                  Delete pages
    delete_zero_length_streams    Delete zero length stream objects
    help                          Prints this message or the help of the given subcommand(s)
    process                       Process PDF document with specified operations
    prune_objects                 Prune unused objects
    renumber_objects              Renumber objects

Output

> ./pdfutil help delete_pages

pdfutil-delete_pages
Delete pages

USAGE:
    pdfutil delete_pages [OPTIONS] [page numbers]

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

OPTIONS:
    -i, --input <input file>
    -o, --output <output file>

ARGS:
    <page numbers>    e.g. 3,5,7-9

Output

Parse TOML into your own structs using Serde:

extern crate serde;
// extern crate serde_json;

extern crate toml;
use std::collections::BTreeMap;

use std::fs::File;
use std::env;
use std::io::{Result, Read};

#[derive(Serialize, Deserialize, Debug)]
pub struct Authorization {
    pub origins: Vec<String>
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ApiInfo {
    pub provider: String,
    pub url: String,
    pub params: Vec<String>,
    pub format: Option<String>
}

#[derive(Serialize, Deserialize, Debug)]
pub struct ApiCollection {
    pub authorization: Authorization,
    pub api: BTreeMap<String, Vec<ApiInfo>>
}

pub fn load_config() -> Result<ApiCollection> {
    let config_file = env::current_dir()?.join("config/apis.toml");
    println!("Load {}", config_file.display());

    let mut input = String::new();
    let mut file = File::open(&config_file)?;
    file.read_to_string(&mut input)?;

    // let deserialized: ApiCollection = serde_json::from_str(&input).unwrap();
    let deserialized: ApiCollection = toml::from_str(&input).unwrap();
    // println!(" -> {:?}", deserialized);
    return Ok(deserialized);
}

apis.toml

[authorization]
origins = ["http://192.168.2.204:3000", "http://www.yunluwang.com"]

[[api.GetIpInfo]]
provider = "taobao"
url = "http://ip.taobao.com/service/getIpInfo.php{?ip}"
params = ["ip"]
format = "json"

[[api.GetIpInfo]]
provider = "sina"
url = "http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json{&ip}"
params = ["ip"]
format = "json"

[[api.GetIpInfo]]
provider = "pconline"
url = "http://whois.pconline.com.cn/ipJson.jsp?json=true{&ip}"
params = ["ip"]
format = "json"

Pastebin application in Rocket:

#![feature(plugin)]
#![plugin(rocket_codegen)]

extern crate rocket;
extern crate rand;

mod paste_id;

use std::io;
use std::fs::File;
use std::path::Path;

use rocket::Data;
use rocket::response::content;

use paste_id::PasteID;

const HOST: &'static str = "http://localhost:8000";
const ID_LENGTH: usize = 3;

#[post("/", data = "<paste>")]
fn upload(paste: Data) -> io::Result<String> {
    let id = PasteID::new(ID_LENGTH);
    let filename = format!("upload/{id}", id = id);
    let url = format!("{host}/{id}\n", host = HOST, id = id);

    paste.stream_to_file(Path::new(&filename))?;
    Ok(url)
}

#[get("/<id>")]
fn retrieve(id: PasteID) -> Option<content::Plain<File>> {
    let filename = format!("upload/{id}", id = id);
    File::open(&filename).map(|f| content::Plain(f)).ok()
}

#[get("/")]
fn index() -> &'static str {
    "
    USAGE
      POST /
          accepts raw data in the body of the request and responds with a URL of
          a page containing the body's content
          EXMAPLE: curl --data-binary @file.txt http://localhost:8000
      GET /<id>
          retrieves the content for the paste with id `<id>`
    "
}

fn main() {
    rocket::ignite().mount("/", routes![index, upload, retrieve]).launch()
}

paste_id.rs

use std::fmt;
use std::borrow::Cow;

use rocket::request::FromParam;
use rand::{self, Rng};

/// Table to retrieve base62 values from.
const BASE62: &'static [u8] = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

/// A _probably_ unique paste ID.
pub struct PasteID<'a>(Cow<'a, str>);

impl<'a> PasteID<'a> {
    /// Generate a _probably_ unique ID with `size` characters. For readability,
    /// the characters used are from the sets [0-9], [A-Z], [a-z]. The
    /// probability of a collision depends on the value of `size`. In
    /// particular, the probability of a collision is 1/62^(size).
    pub fn new(size: usize) -> PasteID<'static> {
        let mut id = String::with_capacity(size);
        let mut rng = rand::thread_rng();
        for _ in 0..size {
            id.push(BASE62[rng.gen::<usize>() % 62] as char);
        }

        PasteID(Cow::Owned(id))
    }
}

impl<'a> fmt::Display for PasteID<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// Returns `true` if `id` is a valid paste ID and `false` otherwise.
fn valid_id(id: &str) -> bool {
    id.chars().all(|c| {
        (c >= 'a' && c <= 'z')
            || (c >= 'A' && c <= 'Z')
            || (c >= '0' && c <= '9')
    })
}

/// Returns an instance of `PasteID` if the path segment is a valid ID.
/// Otherwise returns the invalid ID as the `Err` value.
impl<'a> FromParam<'a> for PasteID<'a> {
    type Error = &'a str;

    fn from_param(param: &'a str) -> Result<PasteID<'a>, &'a str> {
        match valid_id(param) {
            true => Ok(PasteID(Cow::Borrowed(param))),
            false => Err(param)
        }
    }
}

Major Projects

My Projects

  • pom - PEG parser combinators
  • lopdf – PDF document manipulation
  • rddl - Refined Data Description Language (RDDL)
  • ApiProxy - Proxy Web APIs which don't support CORS

HOW TO LEARN RUST

Read two books:

KEEP UP ON

Made with Slides.com