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
fnmain() { println!("Hello,?world!"); }
fnmain() { // Program entry point letmut 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!(); }
letmut s2: String = String::from("Hello "); println!("s2: {s2}"); s2.push_str(s1); println!("s2: {s2}"); lets3: &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
fngenerate_random_number() ->i32 { 4// Chosen by fair dice roll. Guaranteed to be random. }
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.
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
fnmain() { fizzbuzz_to(20); // Defined below, no forward declaration needed }
fnis_divisible_by(lhs: u32, rhs: u32) ->bool { if rhs == 0 { returnfalse; // Corner case, early return } lhs % rhs == 0// The last expression in a block is the return value }
fnfizzbuzz(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}"), } }
#[rustfmt::skip] fninspect(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] fnmain() { letpair = (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 ().
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.
fnmain() { letmut a: i32 = 10; letb: &i32 = &a;
{ letc: &muti32 = &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)] structPoint(i32, i32);
fnleft_most<'a>(p1: &'a Point, p2: &'a Point) -> &'a Point { if p1.0 < p2.0 { p1 } else { p2 } }
'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.
fnmain() { lettext = String::from("The quick brown fox jumps over the lazy dog."); letfox = Highlight(&text[4..19]); letdog = Highlight(&text[35..43]); // erase(text); // cannot move out of `text` because it is borrowed println!("{fox:?}"); println!("{dog:?}"); }
struct
structPerson { name: String, age: u8, }
fnmain() { letmut 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); letjackie = 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); }
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.
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;
fnmain() { letmut 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()); }
forbookin ["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. forbookin ["Pride and Prejudice", "Alice's Adventure in Wonderland"] { letpage_count: &muti32 = page_counts.entry(book.to_string()).or_insert(0); *page_count += 1; }
fnmain() { letmut a = Rc::new(10); letmut b = a.clone();
println!("a: {a}"); println!("b: {b}"); }
mod
精读到此
?
// Tuples can be used as function arguments and as return values fnreverse(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)]//? structMatrix(f32, f32, f32, f32);
fnmain() { // A tuple with a bunch of different types letlong_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 lettuple_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
letpair = (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 lettuple = (1, "hello", 4.5, true);
let (a, b, c, d) = tuple; println!("{:?}, {:?}, {:?}, {:?}", a, b, c, d);
// All elements can be initialized to the same value letys: [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.