安装

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

检查环境变量

export PATH="$HOME/.cargo/bin:$PATH"

rust ecosystem

  • rustc: the Rust compiler which turns .rs files into binaries and other intermediate formats.
  • cargo: the Rust dependency manager and build tool. Cargo knows how to download dependencies hosted on https://crates.io and it will pass them to rustc when building your project. Cargo also comes with a built-in test runner which is used to execute unit tests.
  • rustup: the Rust toolchain installer and updater. This tool is used to install and update rustc and cargo when new versions of Rust is released. In addition, rustup can also download documentation for the standard library. You can have multiple versions of Rust installed at once and rustup will let you switch between them as needed.

hello world

cargo new coding
cd coding
cargo run
cargo test
cargo build
cargo build --release

You can add dependencies for your project by editing Cargo.toml. When you run cargo commands, it will automatically download and compile missing dependencies for you.

syntax

fn main() {
println!("Hello,?world!");
}
fn main() {              // Program entry point
let mut x: i32 = 6; // Mutable variable binding
print!("{x}"); // Macro for printing
while x != 1 { // No parenthesis around expression
if x % 2 == 0 { // Math like in other languages
x = x / 2;
} else {
x = 3 * x + 1;
}
print!(" -> {x}");
}
println!();
}
  • interger default to be i32
  • string default to be utf-8
  • var default to be immutable

compile time guarantees

  • No uninitialized variables.
  • No memory leaks (mostly, see notes).
  • No double-frees.
  • No use-after-free.
  • No NULL pointers.
  • No forgotten locked mutexes.
  • No data races between threads.
  • No iterator invalidation.

runtime guarantees

  • No undefined behavior at runtime:

    • Array access is bounds checked.

    • Integer overflow is defined.

loops

fn main() {
let array = [10, 20, 30];
print!("Iterating over array:");
for n in array {
print!(" {n}");
}
println!();

print!("Iterating over range:");
for i in 0..3 {
print!(" {}", array[i]);
}
println!();
}

iterator

fn main() {
let v: Vec<i8> = vec![10, 20, 30];
let mut iter = v.iter();

println!("v[0]: {:?}", iter.next());
println!("v[1]: {:?}", iter.next());
println!("v[2]: {:?}", iter.next());
println!("No more items: {:?}", iter.next());
}

const

const DIGEST_SIZE: usize = 3;
const ZERO: Option<u8> = Some(42);

fn compute_digest(text: &str) -> [u8; DIGEST_SIZE] {
let mut digest = [ZERO.unwrap_or(0); DIGEST_SIZE];
for (idx, &b) in text.as_bytes().iter().enumerate() {
digest[idx % DIGEST_SIZE] = digest[idx % DIGEST_SIZE].wrapping_add(b);
}
digest
}

fn main() {
let digest = compute_digest("Hello");
println!("Digest: {digest:?}");
}

static

static BANNER: &str = "Welcome to RustOS 3.14";

fn main() {
println!("{BANNER}");
}
  • Mention that const behaves semantically similar to C++’s constexpr.
  • static, on the other hand, is much more similar to a const or mutable global variable in C++.
  • It isn’t super common that one would need a runtime evaluated constant, but it is helpful and safer than using a static.

shadow

fn main() {
let a = 1;
let b = &a;
let a = a + 1;
println!("{a} {b}");
}
// 2 1

types

Types Literals
Signed integers i8, i16, i32, i64, i128, isize -10, 0, 1_000, 123i64
Unsigned integers u8, u16, u32, u64, u128, usize 0, 123, 10u16
Floating point numbers f32, f64 3.14, -10.0e20, 2f32
Strings &str "foo", r#"\\"#
Unicode scalar values char 32 bit wide 'a', 'α', '∞'
Byte strings &[u8] b"abc", br#" " "#
Booleans bool 8 bit wide true, false
Types Literals
Arrays [T; N] [20, 30, 40], [0; 3]
Tuples (), (T,), (T1, T2), … (), ('x',), ('x', 1.2), …

array

fn main() {
let mut a: [i8; 10] = [42; 10];
a[5] = 0;
println!("a: {:?}", a);
}

//a: [42, 42, 42, 42, 42, 0, 42, 42, 42, 42]
  • Arrays have elements of the same type, T, and length, N, which is a compile-time constant. Note that the length of the array is part of its type
  • {:?} gives the debug output.
  • Adding #, eg {a:#?}, invokes a “pretty printing” format, which can be easier to read.(竖着打印?)

tuple

fn main() {
let t: (i8, bool) = (7, true);
println!("1st index: {}", t.0);
println!("2nd index: {}", t.1);
}
  • Like arrays, tuples have a fixed length.
  • Tuples group together values of different types into a compound type.

slices

fn main() {
let a: [i32; 6] = [10, 20, 30, 40, 50, 60];
println!("a: {a:?}");

let s: &[i32] = &a[2..4];
println!("s: {s:?}");
}
  • slices always borrow

string and str

fn main() {
let s1: &str = "World";
println!("s1: {s1}");

let mut s2: String = String::from("Hello ");
println!("s2: {s2}");
s2.push_str(s1);
println!("s2: {s2}");

let s3: &str = &s2[6..];
println!("s3: {s3}");
}
/*
s1: World
s2: Hello
s2: Hello World
s3: World
*/
  • &str an immutable reference to a string slice.
  • String a mutable string buffer.
  • As with many other types String::from() creates a string from a string literal; String::new() creates a new empty string, to which string data can be added using the push() and push_str() methods.
  • The format!() macro is a convenient way to generate an owned string from dynamic values. It accepts the same format specification as println!().

enums

fn generate_random_number() -> i32 {
4 // Chosen by fair dice roll. Guaranteed to be random.
}

#[derive(Debug)]
enum CoinFlip {
Heads,
Tails,
}

fn flip_coin() -> CoinFlip {
let random_number = generate_random_number();
if random_number % 2 == 0 {
return CoinFlip::Heads;
} else {
return CoinFlip::Tails;
}
}

fn main() {
println!("You got: {:?}", flip_coin());
}
enum WebEvent {
PageLoad, // Variant without payload
KeyPress(char), // Tuple struct variant
Click { x: i64, y: i64 }, // Full struct variant
}

#[rustfmt::skip]
fn inspect(event: WebEvent) {
match event {
WebEvent::PageLoad => println!("page loaded"),
WebEvent::KeyPress(c) => println!("pressed '{c}'"),
WebEvent::Click { x, y } => println!("clicked at x={x}, y={y}"),
}
}

fn main() {
let load = WebEvent::PageLoad;
let press = WebEvent::KeyPress('x');
let click = WebEvent::Click { x: 20, y: 80 };

inspect(load);
inspect(press);
inspect(click);
}
  • The match expression has a value. The value is the last expression in the match arm which was executed.

reference

fn main() {
let mut x: i32 = 10;
let ref_x: &mut i32 = &mut x; //&mut x 的&mut 是必须要有的
*ref_x = 20;
println!("x: {x}");
}
  • Be sure to note the difference between let mut ref_x: &i32 and let ref_x: &mut i32. The first one represents a mutable reference which can be bound to different values, while the second represents a reference to a mutable value.
fn main() {
let ref_x: &i32;
{
let x: i32 = 10;
ref_x = &x; //error
}
println!("ref_x: {ref_x}");
}
  • A reference is said to “borrow” the value it refers to.
  • Rust is tracking the lifetimes of all references to ensure they live long enough.

function

fn main() {
fizzbuzz_to(20); // Defined below, no forward declaration needed
}

fn is_divisible_by(lhs: u32, rhs: u32) -> bool {
if rhs == 0 {
return false; // Corner case, early return
}
lhs % rhs == 0 // The last expression in a block is the return value
}

fn fizzbuzz(n: u32) -> () { // No return value means returning the unit type `()`
match (is_divisible_by(n, 3), is_divisible_by(n, 5)) {
(true, true) => println!("fizzbuzz"),
(true, false) => println!("fizz"),
(false, true) => println!("buzz"),
(false, false) => println!("{n}"),
}
}

fn fizzbuzz_to(n: u32) { // `-> ()` is normally omitted
for i in 1..=n {
fizzbuzz(i);
}
}
  • The last expression in a function body (or any block) becomes the return value. Simply omit the ; at the end of the expression.
  • Some functions have no return value, and return the ‘unit type’, (). The compiler will infer this if the -> () return type is omitted.
  • The range expression in the for loop in fizzbuzz_to() contains =n, which causes it to include the upper bound. 去掉=就不包含 upper bound

overloading

不允许重载, 不允许默认参数(可以用 macro?)

可以用泛型

methods

they are simply functions that are associated with a particular type.

struct Rectangle {
width: u32,
height: u32,
}

impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}

fn inc_width(&mut self, delta: u32) {
self.width += delta;
}
}

fn main() {
let mut rect = Rectangle { width: 10, height: 5 };
println!("old area: {}", rect.area());
rect.inc_width(5);
println!("new area: {}", rect.area());
}

match

// destructure

enum Result {
Ok(i32),
Err(String),
}

fn divide_in_two(n: i32) -> Result {
if n % 2 == 0 {
Result::Ok(n / 2)
} else {
Result::Err(format!("cannot divide {n} into two equal parts"))
}
}

fn main() {
let n = 100;
match divide_in_two(n) {
Result::Ok(half) => println!("{n} divided in two is {half}"),
Result::Err(msg) => println!("sorry, an error happened: {msg}"),
}
}

// array

fn main() {
inspect(&[0, -2, 3]);
inspect(&[0, -2, 3, 4]);
}

#[rustfmt::skip]
fn inspect(slice: &[i32]) {
println!("Tell me about {slice:?}");
match slice {
&[0, y, z] => println!("First is 0, y = {y}, and z = {z}"),
&[1, ..] => println!("First is 1 and the rest were ignored"),
_ => println!("All elements were ignored"),
}
}

#[rustfmt::skip]
fn main() {
let pair = (2, -2);
println!("Tell me about {pair:?}");
match pair {
(x, y) if x == y => println!("These are twins"),
(x, y) if x + y == 0 => println!("Antimatter, kaboom!"),
(x, _) if x % 2 == 1 => println!("The first one is odd"),
_ => println!("No correlation..."),
}
}

The _ pattern is a wildcard pattern which matches any value.

block

A block in Rust has a value and a type: the value is the last expression of the block.

However if the last expression ends with ;, then the resulting value and type is ().

while

fn main() {
let v = vec![10, 20, 30];
let mut iter = v.into_iter();

while let Some(x) = iter.next() {
println!("x: {x}");
}
}

标签

fn main() {
let v = vec![10, 20, 30];
let mut iter = v.into_iter();
'outer: while let Some(x) = iter.next() {
println!("x: {x}");
let mut i = 0;
while i < x {
println!("x: {x}, i: {i}");
i += 1;
if i == 3 {
break 'outer;
}
}
}
}

memory

Full control and safety via compile time enforcement of correct memory management.

stack

fn main() {
let s1 = String::from("Hello");
}

s1 stack heap
┌────────┬──────┐ ┌─────┐
│ptr │ ───┼───────▶│hello│
│len │ 5 │ └─────┘
│capacity│ 5
└───────────────┘

ownership

All variable bindings have a scope where they are valid and it is an error to use a variable outside its scope:

struct Point(i32, i32);

fn main() {
{
let p = Point(3, 4);
println!("x: {}", p.0);
}
println!("y: {}", p.1);// error
}

move

An assignment will transfer ownership between variables: the data is moved when assignment

fn main() {
let s1: String = String::from("Hello!");
let s2: String = s1;
println!("s2: {s2}");
// println!("s1: {s1}"); error
}
  • There is always exactly one variable binding which owns a value.
  • In Rust, clones are explicit (by using clone).
  • When you pass a value to a function, the value is assigned to the function parameter. This transfers ownership.
fn say_hello(name: String) {
println!("Hello {name}")
}

fn main() {
let name = String::from("Alice");
say_hello(name);
// say_hello(name); error, 已经转移到函数里
}
  • move 是默认的, 但是有些类型 copy 是默认的, 比如 int 等
  • Copying and cloning are not the same thing:
    • Copying refers to bitwise copies of memory regions and does not work on arbitrary objects.
    • Copying does not allow for custom logic (unlike copy constructors in C++).
    • Cloning is a more general operation and also allows for custom behavior by implementing the Clone trait.
    • Copying does not work on types that implement the Drop trait.

borrow

Instead of transferring ownership when calling a function, you can let a function borrow the value:

#[derive(Debug)]
struct Point(i32, i32);

fn add(p1: &Point, p2: &Point) -> Point {
Point(p1.0 + p2.0, p1.1 + p2.1)
}

fn main() {
let p1 = Point(3, 4);
let p2 = Point(10, 20);
let p3 = add(&p1, &p2);
println!("{p1:?} + {p2:?} = {p3:?}");
}
  • The add function borrows two points and returns a new point.
  • The caller retains ownership of the inputs.

Rust puts constraints on the ways you can borrow values:

  • You can have one or more &T values at any given time, or
  • You can have exactly one &mut T value.
fn main() {
let mut a: i32 = 10;
let b: &i32 = &a;

{
let c: &mut i32 = &mut a; //error
*c = 20;
}

println!("a: {a}");
println!("b: {b}");
}
  • The above code does not compile because a is borrowed as mutable (through c) and as immutable (through b) at the same time.
  • Move the println! statement for b before the scope that introduces c to make the code compile.
  • After that change, the compiler realizes that b is only ever used before the new mutable borrow of a through c. This is a feature of the borrow checker called “non-lexical lifetimes”.

lifetime

  • Read &'a Point as “a borrowed Point which is valid for at least the lifetime a”.
  • Lifetimes are always inferred by the compiler: you cannot assign a lifetime yourself.
    • Lifetime annotations create constraints; the compiler verifies that there is a valid solution.
#[derive(Debug)]
struct Point(i32, i32);

fn left_most<'a>(p1: &'a Point, p2: &'a Point) -> &'a Point {
if p1.0 < p2.0 { p1 } else { p2 }
}

fn main() {
let p1: Point = Point(10, 10);
let p2: Point = Point(20, 20);
let p3: &Point = left_most(&p1, &p2);
println!("left-most point: {:?}", p3);
}
  • 'a is a generic parameter, it is inferred by the compiler.
  • Lifetimes start with ' and 'a is a typical default name.
  • Another way to explain it:
    • Two references to two values are borrowed by a function and the function returns another reference.
    • It must have come from one of those two inputs (or from a global variable).
    • Which one is it? The compiler needs to to know, so at the call site the returned reference is not used for longer than a variable from where the reference came from.
#[derive(Debug)]
struct Highlight<'doc>(&'doc str);

fn erase(text: String) {
println!("Bye {text}!");
}

fn main() {
let text = String::from("The quick brown fox jumps over the lazy dog.");
let fox = Highlight(&text[4..19]);
let dog = Highlight(&text[35..43]);
// erase(text);
// cannot move out of `text` because it is borrowed
println!("{fox:?}");
println!("{dog:?}");
}

struct

struct Person {
name: String,
age: u8,
}

fn main() {
let mut peter = Person {
name: String::from("Peter"),
age: 27,
};
println!("{} is {} years old", peter.name, peter.age);

peter.age = 28;
println!("{} is {} years old", peter.name, peter.age);

let jackie = Person {
name: String::from("Jackie"),
..peter
// the syntax ..peter allows us to copy the majority of the fields from the old struct without having to explicitly type it all out. It must always be the last element.
};
println!("{} is {} years old", jackie.name, jackie.age);
}

struct Point(i32, i32);

fn main() {
let p = Point(17, 23);
println!("({}, {})", p.0, p.1);
}
  • Unlike in C++, there is no inheritance between structs.

tuple

struct Point(i32, i32);

fn main() {
let p = Point(17, 23);
println!("({}, {})", p.0, p.1);
}
#[derive(Debug)]
struct Person {
name: String,
age: u8,
}

impl Person {
fn new(name: String, age: u8) -> Person {
Person { name, age }
}
}

//or

impl Person {
fn new(name: String, age: u8) -> Self {
Self { name, age }
}
}

fn main() {
let peter = Person::new(String::from("Peter"), 27);
println!("{peter:?}");
}

default

#[derive(Debug)]
struct Person {
name: String,
age: u8,
}
impl Default for Person {
fn default() -> Person {
Person {
name: "Bot".to_string(),
age: 0,
}
}
}
fn create_default() {
let tmp = Person {
..Default::default()
};
let tmp = Person {
name: "Sam".to_string(),
..Default::default()
};
}
  • Methods are defined in the impl block.
  • Use {:#?} when printing structs to request the Debug representation.

methods

You do this with an impl block:

#[derive(Debug)]
struct Person {
name: String,
age: u8,
}

impl Person {
fn say_hello(&self) {
println!("Hello, my name is {}", self.name);
}

fn say_goodbye(&self){
println!("gb, my name is {}", self.name);
}
}

fn main() {
let peter = Person {
name: String::from("Peter"),
age: 27,
};
peter.say_hello();
peter.say_goodbye()
}
  • Point out the use of the keyword self, a method receiver.

    • Show that it is an abbreviated term for self:&Self and perhaps show how the struct name could also be used.
    • Explain that Self is a type alias for the type the impl block is in and can be used elsewhere in the block.
    • Note how self is used like other structs and dot notation can be used to refer to individual fields.
    • This might be a good time to demonstrate how the &self differs from self by modifying the code and trying to run say_hello twice.
  • The &self above indicates that the method borrows the object immutably. There are other possible receivers for a method:

    • &self: borrows the object from the caller using a shared and immutable reference. The object can be used again afterwards.
    • &mut self: borrows the object from the caller using a unique and mutable reference. The object can be used again afterwards.
    • self: takes ownership of the object and moves it away from the caller. The method becomes the owner of the object. The object will be dropped (deallocated) when the method returns, unless its ownership is explicitly transmitted.
    • mut self: same as above, but while the method owns the object, it can mutate it too. Complete ownership does not automatically mean mutability.
    • No receiver: this becomes a static method on the struct. Typically used to create constructors which are called new by convention.

    Beyond variants on self, there are also special wrapper types allowed to be receiver types, such as Box<Self>.

  • Note that although the method receivers are different, the non-static functions are called the same way in the main body. Rust enables automatic referencing and dereferencing when calling methods. Rust automatically adds in the &, *, muts so that that object matches the method signature.

standard library

option and result

fn main() {
let numbers = vec![10, 20, 30];
let first: Option<&i8> = numbers.first();
println!("first: {first:?}");

let idx: Result<usize, usize> = numbers.binary_search(&10);
println!("idx: {idx:?}");
}
  • Option<&T> has zero space overhead compared to &T.
  • Result is the standard type to implement error handling as we will see on Day 3.
  • binary_search returns Result<usize, usize>
    • If found, Result::Ok holds the index where the element is found.
    • Otherwise, Result::Err contains the index where such an element should be inserted.

string

fn main() {
let mut s1 = String::new();
s1.push_str("Hello");
println!("s1: len = {}, capacity = {}", s1.len(), s1.capacity());

let mut s2 = String::with_capacity(s1.len() + 1);
s2.push_str(&s1);
s2.push('!');
println!("s2: len = {}, capacity = {}", s2.len(), s2.capacity());

let s3 = String::from("🇨🇭");
println!("s3: len = {}, number of chars = {}", s3.len(),
s3.chars().count());
}
  • String implements Deref<Target = str> which means that you can call all str methods on a String.
  • When people refer to strings they could either be talking about &str or String.

Vec

fn main() {
let mut v1 = Vec::new();
v1.push(42);
println!("v1: len = {}, capacity = {}", v1.len(), v1.capacity());

let mut v2 = Vec::with_capacity(v1.len() + 1);
v2.extend(v1.iter());
v2.push(9999);
println!("v2: len = {}, capacity = {}", v2.len(), v2.capacity());

// Canonical macro to initialize a vector with elements.
let mut v3 = vec![0, 0, 1, 2, 3, 4];

// Retain only the even elements.
v3.retain(|x| x % 2 == 0);
println!("{v3:?}");

// Remove consecutive duplicates.
v3.dedup();
println!("{v3:?}");
}
  • The data it contains is stored on the heap. This means the amount of data doesn’t need to be known at compile time. It can grow or shrink at runtime.

hashmap

use std::collections::HashMap;

fn main() {
let mut page_counts = HashMap::new();
page_counts.insert("Adventures of Huckleberry Finn".to_string(), 207);
page_counts.insert("Grimms' Fairy Tales".to_string(), 751);
page_counts.insert("Pride and Prejudice".to_string(), 303);

if !page_counts.contains_key("Les Misérables") {
println!("We know about {} books, but not Les Misérables.",
page_counts.len());
}

for book in ["Pride and Prejudice", "Alice's Adventure in Wonderland"] {
match page_counts.get(book) {
Some(count) => println!("{book}: {count} pages"),
None => println!("{book} is unknown.")
}
}

// Use the .entry() method to insert a value if nothing is found.
for book in ["Pride and Prejudice", "Alice's Adventure in Wonderland"] {
let page_count: &mut i32 = page_counts.entry(book.to_string()).or_insert(0);
*page_count += 1;
}

println!("{page_counts:#?}");
}

box

fn main() {
let five = Box::new(5);
println!("five: {}", *five);
}

a box is never null

Rc

use std::rc::Rc;

fn main() {
let mut a = Rc::new(10);
let mut b = a.clone();

println!("a: {a}");
println!("b: {b}");
}

mod

精读到此

?

// Tuples can be used as function arguments and as return values
fn reverse(pair: (i32, bool)) -> (bool, i32) {
// `let` can be used to bind the members of a tuple to variables
let (int_param, bool_param) = pair;

(bool_param, int_param)
//return ?
}

// The following struct is for the activity.
#[derive(Debug)]//?
struct Matrix(f32, f32, f32, f32);

fn main() {
// A tuple with a bunch of different types
let long_tuple = (1u8, 2u16, 3u32, 4u64,
-1i8, -2i16, -3i32, -4i64,
0.1f32, 0.2f64,
'a', true);

// Values can be extracted from the tuple using tuple indexing
println!("long tuple first value: {}", long_tuple.0);// .
println!("long tuple second value: {}", long_tuple.1);

// Tuples can be tuple members
let tuple_of_tuples = ((1u8, 2u16, 2u32), (4u64, -1i8), -2i16);

// Tuples are printable
//{:?}?
println!("tuple of tuples: {:?}", tuple_of_tuples);

// But long Tuples (more than 12 elements) cannot be printed

let pair = (1, true);
println!("pair is {:?}", pair);

println!("the reversed pair is {:?}", reverse(pair));

// To create one element tuples, the comma is required to tell them apart
// from a literal surrounded by parentheses
println!("one element tuple: {:?}", (5u32,));
println!("just an integer: {:?}", (5u32));

//tuples can be destructured to create bindings
let tuple = (1, "hello", 4.5, true);

let (a, b, c, d) = tuple;
println!("{:?}, {:?}, {:?}, {:?}", a, b, c, d);

let matrix = Matrix(1.1, 1.2, 2.1, 2.2);
println!("{:?}", matrix);

}

let xs: [i32; 5] = [1, 2, 3, 4, 5];

// All elements can be initialized to the same value
let ys: [i32; 500] = [0; 500];

Slices are similar to arrays, but their length is not known at compile time. Instead, a slice is a two-word object, the first word is a pointer to the data, and the second word is the length of the slice.