TL;DR

enum Result<T, E> {
   Ok(T),
   Err(E),
}

A Result value is one of two variants:

  1. A value written Ok(v), indicating that the parse succeeded and v is the value produced
    • If the result is Ok(v), expect simply returns v itself.
  2. A value written Err(e), indicating that the parse failed and e is an error value explaining why.
    • If the result is an Err(e), expect prints a message that includes a description of e and exits the program immediately.

Rust does not have exceptions: all errors are handled using either Result or panic.

enum Option<T> {
    None,
    Some(T),
}
  • Option is an enumerated type, often called an enum, because its definition enumerates several variants that a value of this type could be: for any type T, a value of type Option<T> is either Some(v), where v is a value of type T, or None, indicating no T value is available.
  • Option is a generic type.

Hello Rust

The best way to install Rust is to use rustup.

# https://rustup.rs/
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
cargo --version
# cargo 1.65.0 (4bc8f24d3 2022-10-20)
rustc --version
# rustc 1.65.0 (897e37553 2022-11-02)
rustdoc --version
# rustdoc 1.65.0 (897e37553 2022-11-02)

# 安装特定版本
rustup install 1.49.0
# 切版本
rustup default 1.49.0
cargo --version
# cargo 1.49.0 (d00d64df9 2020-12-05)
rustc --version
# rustc 1.49.0 (e1884a8e3 2020-12-29)
rustdoc --version
# rustdoc 1.49.0 (e1884a8e3 2020-12-29)

Rust commands:

  • cargo is Rust’s compilation manager, package manager, and general-purpose tool.
    • You can use Cargo to start a new project, build and run your program, and manage any external libraries your code depends on.
  • rustc is the Rust compiler.
    • Usually we let Cargo invoke the compiler for us, but sometimes it’s useful to run it directly.
  • rustdoc is the Rust documentation tool.
    • If you write documentation in comments of the appropriate form in your program’s source code, rustdoc can build nicely formatted HTML from them.
    • Like rustc, we usually let Cargo run rustdoc for us.
    • /// are documentation comments; the rustdoc utility knows how to parse them, together with the code they describe, and produce online documentation.

cargo in action:

  • cargo new repo_name creates a Rust package with some standard metadata.

    • Cargo.toml holds metadata for the package.
    • Use --vsc none to skip the step of setting git metadata.
    • Use --lib flag to create a library crate.
  • Invoke the cargo run command from any directory in the package to build and run our program.

    • Cargo places the executable in the target subdirectory at the top of the package.
  • cargo clean cleans up the generated files.

    cargo new hello
    cd hello
    cargo run
        # Finished dev [unoptimized + debuginfo] target(s) in 0.03s
        #  Running `target/debug/hello`
    # Hello, world!
    cargo clean
    
    # vscode format on save: install rust-analyzer and add the following to settings
    # "[rust]": {
    #     "editor.defaultFormatter": "rust-lang.rust-analyzer"
    # }
    

Functions

The fn keyword (pronounced “fun”) introduces a function; the -> token precedes the return type.

fn gcd(mut n: u64, mut m: u64) -> u64 {
    assert!(n != 0 && m != 0);
    while m != 0 {
        if m < n {
            let t = m;
            m = n;
            n = t;
        }
        m = m % n;
    }
    n
}
  • By default, once a variable is initialized, its value can’t be changed; placing the mut keyword (pronounced “mute,” short for mutable) before the parameters n and m allows our function body to assign to them.
    • In practice, most variables don’t get assigned to.
  • The function’s body starts with a call to the assert! macro.
    • The ! character marks this as a macro invocation, not a function call.
    • Rust’s assert! checks that its argument is true, and if it is not, terminates the program with a helpful message including the source location of the failing check; this kind of abrupt termination is called a panic.
    • Unlike C and C++, in which assertions can be skipped, Rust always checks assertions regardless of how the program was compiled.
    • There is also a debug_assert! macro, whose assertions are skipped when the program is compiled for speed.
  • A let statement declares a local variable.
    • We don’t need to write out t’s type, as long as Rust can infer it from how the variable is used.
      • We can spell out t’s type like this: let t: u64 = m;
    • Rust only infers types within function bodies: you must write out the types of function parameters and return values.
  • Rust has a return statement.
    • If a function body ends with an expression that is not followed by a semicolon, that’s the function’s return value.

      • It’s typical in Rust to use this form to establish the function’s value when control “falls off the end” of the function, and use return statements only for explicit early returns from the midst of a function.
    • Any block surrounded by curly braces can function as an expression.

      {
          println!("evaluating cos x");
          x.cos()
      }
      
      • This is an expression that prints a message and then yields x.cos() as its value.

Four-space indentation is standard Rust style.

Unit Tests

Rust has simple support for testing built into the language.

#[test]
fn test_gcd() {
    assert_eq!(gcd(14, 15), 1);
    assert_eq!(gcd(2 * 3 * 5 * 11 * 17, 3 * 7 * 11 * 13 * 19), 3 * 11);
}
  • The #[test] atop the definition marks test_gcd as a test function, to be skipped in normal compilations, but included and called automatically if we run our program with the cargo test command.
  • We can have test functions scattered throughout our source tree, placed next to the code they exercise, and cargo test will automatically gather them up and run them all.

The #[test] marker is an example of an attribute.

  • Attributes are an open-ended system for marking functions and other declarations with extra information (like attributes in C++ and C#, or annotations in Java).
  • They’re used to control compiler warnings and code style checks, include code conditionally (like #ifdef in C and C++), tell Rust how to interact with code written in other languages, and so on.

Handling Command-line Arguments

// bring in trait
use std::str::FromStr;
// bring in module
use std::env;
fn main() {
    let mut numbers = Vec::new(); // Vec<u64> inferred: push u64; gcd(d, )
    for arg in env::args().skip(1) {
        numbers.push(u64::from_str(&arg).expect("error parsing argument"));
    }
    if numbers.len() == 0 {
        eprintln!("Usage: gcd NUMBER ...");
        std::process::exit(1);
    }
    let mut d = numbers[0];
    for m in &numbers[1..] {
        d = gcd(d, *m);
    }
    println!("The greatest common divisor of {:?} is {}", numbers, d);
}
  • A trait is a collection of methods that types can implement. The first use declaration brings the standard library trait FromStr into scope.
    • Any type that implements the FromStr trait has a from_str method that tries to parse a value of that type from a string. The u64 type implements FromStr.
    • Although we never use the name FromStr elsewhere in the program, a trait must be in scope in order to use its methods (from_str).
  • The second use declaration brings in the std::env module that provides several useful functions and types for interacting with the execution environment (including the args function).
  • Vec is Rust’s growable vector type, analogous to C++’s std::vector, a Python list, or a JavaScript array.
    • Even though vectors are designed to be grown and shrunk dynamically, we must still mark the variable mut for Rust to let us push numbers onto the end of it.
  • The std::env module’s args function returns an iterator, a value that produces each argument on demand, and indicates when we’re done.
    • The first value produced by the iterator returned by args is always the name of the program being run.
    • The iterator’s skip method produces a new iterator that omits that first value.
  • u64::from_str attempts to parse arg as an unsigned 64-bit integer.
    • Rather than a method we’re invoking on some u64 value we have at hand, u64::from_str is a function associated with the u64 type, akin to a static method in C++ or Java.
  • The eprintln! macro writes our error message to the standard error output stream.
  • The & operator in &numbers[1..] borrows a reference to the vector’s elements from the second onward.
    • We are telling Rust that ownership of the vector remains with numbers; we are merely borrowing its elements for the loop.
  • The * operator in *m dereferences m, yielding the value it refers to.
  • The println! macro takes a template string, substitutes formatted versions of the remaining arguments for the {...} forms as they appear in the template string, and writes the result to the standard output stream.
  • Rust assumes that if main returns at all, the program finished successfully.
    • Only by explicitly calling functions like expect or std::process::exit can we cause the program to terminate with an error status code.
    • C and C++ require main to return zero if the program finished successfully, or a nonzero exit status if something went wrong.

from_str returns a Result value that indicates whether the parse succeeded or failed.

enum Result<T, E> {
   Ok(T),
   Err(E),
}

A Result value is one of two variants:

  1. A value written Ok(v), indicating that the parse succeeded and v is the value produced
    • If the result is Ok(v), expect simply returns v itself.
  2. A value written Err(e), indicating that the parse failed and e is an error value explaining why.
    • If the result is an Err(e), expect prints a message that includes a description of e and exits the program immediately.

Rust does not have exceptions: all errors are handled using either Result or panic.

View the standard library documentation in your browser with rustup doc --std.

Serving Pages to the Web

A Rust package, whether a library or an executable, is called a crate. We need only name those crates directly in our Cargo.toml; cargo takes care of bringing in whatever other crates those need in turn.

When we simply request version "1" of a crate in a Cargo.toml file, Cargo will use the newest available version of the crate before 2.0.

[package]
name = "actix-gcd"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "1.0.8"
serde = { version = "1.0", features = ["derive"] }
use actix_web::{web, App, HttpResponse, HttpServer};
fn main() {
    // the whole part inside parentheses is a closure
    let server = HttpServer::new(|| {
        App::new()
            .route("/", web::get().to(get_index))
            .route("gcd", web::post().to(post_gcd))
    });
    println!("Serving on http://localhost:3000...");
    server
        .bind("127.0.0.1:3000")
        .expect("error binding server to address")
        .run()
        .expect("error running server");
}
fn get_index() -> HttpResponse {
    HttpResponse::Ok().content_type("text/html").body(
        r#"
            <title>GCD Calculator</title>
            <form action="/gcd" method="post">
            <input type="text" name="n"/>
            <input type="text" name="m"/>
            <button type="submit">Compute GCD</button>
            </form>
        "#,
    )
}
#[derive(Deserialize)]
struct GcdParameters {
    n: u64,
    m: u64,
}
fn post_gcd(form: web::Form<GcdParameters>) -> HttpResponse {
    if form.n == 0 || form.m == 0 {
        return HttpResponse::BadRequest()
            .content_type("text/html")
            .body("Computing the GCD with zero is boring.");
    }
    let response = format!(
        "The greatest common divisor of the numbers {} and {} \
        is <b>{}</b>\n",
        form.n,
        form.m,
        gcd(form.n, form.m)
    );
    HttpResponse::Ok().content_type("text/html").body(response)
}
  • When we write use actix_web::{...}, each of the names listed inside the curly brackets becomes directly usable in our code; instead of having to spell out the full name actix_web::HttpResponse each time we use it, we can simply refer to it as HttpResponse.
  • || { App::new() ... } is a Rust closure expression. A closure is a value that can be called as if it were a function.
    • This closure takes no arguments, but if it did, their names would appear between the || vertical bars.
    • The { ... } is the body of the closure.
  • When we start our server, Actix starts a pool of threads to handle incoming requests. Each thread calls our closure to get a fresh copy of the App value that tells it how to route and handle requests.
    • The closure calls App::new to create a new, empty App and then calls its route method to add routes for paths.
    • The route method returns the same App it was invoked on, now enhanced with the new route.
    • Since there’s no semicolon at the end of the closure’s body, the App is the closure’s return value, ready for the HttpServer thread to use.
  • Rust “raw string” syntax: the letter r, zero or more hash marks (that is, the # character), a double quote, and then the contents of the string, terminated by another double quote followed by the same number of hash marks.
    • Any character may occur within a raw string without being escaped.
    • We can always ensure the string ends where we intend by using more hash marks around the quotes than ever appear in the text.
  • Placing a #[derive(Deserialize)] attribute above a type definition tells the serde crate to examine the type when the program is compiled and automatically generate code to parse a value of this type from data in the format that HTML forms use for POST requests.
    • Actix knows how to extract a value of any type web::Form<T> from an HTTP request if, and only if, T can be deserialized from HTML form POST data.
    • These relationships between types and functions are all worked out at compile time; if you write a handler function with an argument type that Actix doesn’t know how to handle, the Rust compiler lets you know of your mistake immediately.
  • The format! macro is just like the println! macro, except that instead of writing the text to the standard output, it returns it as a string.

Concurrency

use num::Complex;
fn complex_square_add_loop(c: Complex<f64>) {
    let mut z = Complex { re: 0.0, im: 0.0 };
    // In real life, Rust can see that z is never used for anything and so might not bother computing its value.
    loop {
        z = z * z + c;
    }
}

/// Try to determine if `c` is in the Mandelbrot set, using at most `limit`
/// iterations to decide.
///
/// If `c` is not a member, return `Some(i)`, where `i` is the number of
/// iterations it took for `c` to leave the circle of radius 2 centered on the
/// origin. If `c` seems to be a member (more precisely, if we reached the
/// iteration limit without being able to prove that `c` is not a member),
/// return `None`.
fn escape_time(c: Complex<f64>, limit: usize) -> Option<usize> {
    let mut z = Complex { re: 0.0, im: 0.0 };
    for i in 0..limit {
        if z.norm_sqr() > 4.0 {
            return Some(i);
        }
        z = z * z + c;
    }
    None
}
  • The Mandelbrot set is defined as the set of complex numbers c for which z does not fly out to infinity. Some experimentation shows that if c is greater than 0.25 or less than –2.0, then z eventually becomes infinitely large; otherwise, it stays somewhere in the neighborhood of zero.
  • Since a complex number c has both real and imaginary components c.re and c.im, we’ll treat these as the x and y coordinates of a point on the Cartesian plane, and color the point black if c is in the Mandelbrot set, or a lighter color otherwise. So for each pixel in our image, we must run the preceding loop on the corresponding point on the complex plane, see whether it escapes to infinity or orbits around the origin forever, and color it accordingly.
    • If we give up on running the loop forever and just try some limited number of iterations, it turns out that we still get a decent approximation of the set. How many iterations we need depends on how precisely we want to plot the boundary.
    • It’s been shown that, if z ever once leaves the circle of radius 2 centered at the origin, it will definitely fly infinitely far away from the origin eventually.
enum Option<T> {
    None,
    Some(T),
}
  • Option is an enumerated type, often called an enum, because its definition enumerates several variants that a value of this type could be: for any type T, a value of type Option<T> is either Some(v), where v is a value of type T, or None, indicating no T value is available.
  • Option is a generic type.
struct Complex<T> {
    /// Real portion of the complex number
    re: T,
    /// Imaginary portion of the complex number
    im: T,
}
  • Read the <T> after the type name (Complex<T>) as “for any type T.”
use std::str::FromStr;
/// Parse the string `s` as a coordinate pair, like `"400x600"` or `"1.0,0.5"`.
///
/// Specifically, `s` should have the form <left><sep><right>, where <sep> is
/// the character given by the `separator` argument, and <left> and <right> are
/// both strings that can be parsed by `T::from_str`. `separator` must be an
/// ASCII character.
///
/// If `s` has the proper form, return `Some<(x, y)>`. If it doesn't parse
/// correctly, return `None`.
fn parse_pair<T: FromStr>(s: &str, separator: char) -> Option<(T, T)> {
    match s.find(separator) {
        None => None,
        // index is the separator’s position in the string
        Some(index) => {
            match (T::from_str(&s[..index]), T::from_str(&s[index + 1..])) {
                // this pattern matches only if both elements of the tuple are Ok variants of the Result type
                (Ok(l), Ok(r)) => Some((l, r)),
                _ => None
            }
        }
    }
}
#[test]
fn test_parse_pair() {
    assert_eq!(parse_pair::<i32>("", ','), None);
    assert_eq!(parse_pair::<i32>("10,", ','), None);
    assert_eq!(parse_pair::<i32>(",10", ','), None);
    assert_eq!(parse_pair::<i32>("10,20", ','), Some((10, 20)));
    assert_eq!(parse_pair::<i32>("10,20xy", ','), None);
    assert_eq!(parse_pair::<f64>("0.5x", 'x'), None);
    assert_eq!(parse_pair::<f64>("0.5x1.5", 'x'), Some((0.5, 1.5)));
}

/// Parse a pair of floating-point numbers separated by a comma as a complex
/// number.
fn parse_complex(s: &str) -> Option<Complex<f64>> {
    match parse_pair(s, ',') {
        Some((re, im)) => Some(Complex { re, im }),
        None => None
    }
}
#[test]
fn test_parse_complex() {
    assert_eq!(parse_complex("1.25,-0.0625"),
               Some(Complex { re: 1.25, im: -0.0625 }));
    assert_eq!(parse_complex(",-0.0625"), None);
}
  • parse_pair is a generic function. Read the <T: FromStr> as “For any type T that implements the FromStr trait…”
    • This effectively lets us define an entire family of functions at once: parse_pair::<i32> is a function that parses pairs of i32 values, parse_pair::<f64> parses pairs of floating-point values, and so on.
  • T a type parameter of parse_pair. When you use a generic function, Rust will often infer type parameters of a generic function.
    • This is very much like a function template in C++.
  • The parse_pair function doesn’t use an explicit return statement, so its return value is the value of the last (and the only) expression in its body.
  • The wildcard pattern _ matches anything and ignores its value.
fn pixel_to_point(bounds: (usize, usize),
                  pixel: (usize, usize),
                  upper_left: Complex<f64>,
                  lower_right: Complex<f64>)
    -> Complex<f64>
  • pixel.0 refers to the first element of the tuple pixel.
  • pixel.0 as f64 converts pixel.0 to an f64 value.
    • Rust generally refuses to convert between numeric types implicitly.

The unit type (zero-tuple) () has only one value, also written (). The unit type is akin to void in C and C++.

Handle File::create’s result:

let output = match File::create(filename) {
    Ok(f) => f,
    Err(e) => {
        return Err(e);
    }
};
// shorthand
let output = File::create(filename)?;
  • On success, let output be the File carried in the Ok value. On failure, pass along the error to the caller.
  • This kind of match statement is such a common pattern in Rust that the language provides the ? operator as shorthand for the whole thing.
    • Attempting to use ? in the main function won’t work because it doesn’t return a value. Use a match statement, or one of the shorthand methods like unwrap and expect. There’s also the option of simply changing main to return a Result.

The macro call vec![v; n] creates a vector n elements long whose elements are initialized to v.

The crossbeam crate provides a number of valuable concurrency facilities.

crossbeam::scope(|spawner| {
    // ...
}).unwrap();

spawner.spawn(move |_| {
    render(band, band_bounds, band_upper_left, band_lower_right);
});
  • The argument |spawner| { ... } is a Rust closure that expects a single argument, spawner.
    • Unlike functions declared with fn, we don’t need to declare the types of a closure’s arguments; Rust will infer them, along with its return type.
    • crossbeam::scope calls the closure, passing as the spawner argument a value the closure can use to create new threads.
      • 调用方传 spawner 参数给闭包,这一参数不用手动创建。
    • The crossbeam::scope function waits for all such threads to finish execution before returning itself.
  • The move keyword at the front indicates that this closure takes ownership of the variables it uses.
    • The argument list |_| means that the closure takes one argument, which it doesn’t use (another spawner for making nested threads).

The num_cpus crate provides a function that returns the number of CPUs available on the current system.

Filesystems and Command-Line Tools

#[derive(Debug)]
struct Arguments {
    target: String,
    replacement: String,
    filename: String,
    output: String,
}
fn main() {
    let args = parse_args();
    println!("{:?}", args);

    // println!("{}", args);
    // = help: the trait `std::fmt::Display` is not implemented for `Arguments`
    // = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
}
// cargo run "find" "replace" file output
  • The #[derive(Debug)] attribute tells the compiler to generate some extra code that allows us to format the Arguments struct with {:?} in println!.
use text_colorizer::*;
fn print_usage() {
    eprintln!("{} - change occurrences of one string into another",
              "quickreplace".green());
    eprintln!("Usage: quickreplace <target> <replacement> <INPUT> <OUTPUT>");
}
  • text-colorizer creates colorful output in the terminal.
use regex::Regex;
fn replace(target: &str, replacement: &str, text: &str)
-> Result<String, regex::Error>
{
    let regex = Regex::new(target)?;
    Ok(regex.replace_all(text, replacement).to_string())
}
  • Regex::new compiles the user-provided regex, and it can fail if given an invalid string.
  • If replace_all finds matches, it returns a new String with those matches replaced with the text we gave it. Otherwise, replace_all returns a pointer to the original text, avoiding unnecessary memory allocation and copying. In this case, however, we always want an independent copy, so we use the to_string method to get a String in either case and return that string wrapped in Result::Ok, as in the other functions.

References