The Rust Programming Language - HsuJv/Note GitHub Wiki
sudo pacman -S rustup
rustup default nightly
cargo install racer
echo "export CARGO_HOME=\"$HOME/.cargo\"" >> ~/.bashrc
echo "export RUSTBINPATH=\"$CARGO_HOME/bin\"" >> ~/.bashrc
echo "export RUST=\"$HOME/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu\"" >> ~/.bashrc
echo "export RUST_SRC_PATH=\"$RUST/lib/rustlib/src/rust/src\"" >> ~/.bashrc
echo "export PATH=$PATH:$RUSTBINPATH" >> ~/.bashrc
source ~/.bashrc
cargo new hello_world --bin
cd hello_world
cargo build
cargo run
- You will get a
main.rs
in./hello_world/src
, codes in it are:
fn main() {
println!("Hello, world!");
}
fn main() {
}
- These lines define a function in Rust.
- The
main
function is the entry of the whole program - The
()
has the parameter tables in it - The whole function body is enclosed in a pair of curly bracket
{}
-
println!(...);
:-
println!
calls a macro whileprintln
calls a function. Using a!
in rust means that a macro is called instead of a normal function - rust ends the line with a semicolon(
;
)
-
-
Cargo is Rust's build system and package manager.
-
In the
hello_world
directory, we've also gotCargo.lock
andCargo.toml
-
Cargo.toml
:[package] name = "hello_world" version = "0.1.0" authors = ["********"] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies]
- This file is in the TOML (Tom's Obvious, Minimal Language) format.
-
[package]
section is the package configuring. -
[dependencies]
section is a section that used to list any of this project's dependencies. In Rust, packages of code are refferred to as creates.
-
cargo build
: build a binary in./target/debug/
-
cargo run
: run the binary built before -
cargo check
: check the code but do not build, which is much faster thancargo build
-
cargo build --release
: instead build in./target/debug
with debug symbols, this will build the target in./target/release
with optimization and no symbols in.
use std::io;
fn main() {
println!("Guess the number!");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {}", guess);
}
-
use std::io;
: First bing theio
library into scope, which comes from the standard library (akastd
) -
fn main() {
: the entry of the whole program - The following two
println!
print the hints on the screen -
let mut guess = String::new()
:-
let foo = bar;
: creates a new variable named foo and binds it to the value of bar. In Rust, variable are immutable by default. Somut
is needed for vars that mutable. -
String::new()
: a function that returns a new instance of aString
(a string type provided by the standard library that is a growable, UTF-8 encoded bit of text.) -
::
: calls an associated function (aka static method) of a type.
-
-
io::stdin().read_line(&mut guess).expect("Failed to read line");
:-
io::stdin()
: calls thestdin
function from theio
module. Thestdin
function returns an instance ofstd::io::Stdin
, which is a type that represents a handle to the standard input for the terminal. -
.read_line(&mut guess)
: calls theread_line
method on the standard input handle to get input from the user. We're also passing one argument toread_line
:&mut guess
. The argumentguess
needs to be mutable so the method can change its value. -
&
: indicates that this argument is a reference. One of Rust’s major advantages is how safe and easy it is to use references. -
.expect()
:read_line
returns a value which is the type ofio::Result
. Rust has a number of types named Result in its standard library: a generic Result as well as specific versions for submodules, such as io::Result. TheResult
types are enums, consists ofOk
orErr
. An instance ofio::Result
has anexcept
method. If this instance ofio::Result
is anErr
value,except
will cause the program to crash and display the message that passed as an argument. If this instance ofio::Result
is anOk
value, expect will take the return value thatOk
is holding and return just that value to you so you can use it. -
println!("You guessed: {}", guess);
: The{}
are known as placeholders
-
- A create is a collection of Rust source code files. The project we've built before is a binary create, which is an executable.
- The
rand
create is a library create, which contains code intended to be used in other programs. - Before
rand
create is used, it should be declared as a dependency in Cargo.toml
[dependencies]
rand = "0.3.14"
- Cargo understands Semantic Versioning, which is a standard for writing version numbers. The number
0.3.14
is actually shorthand for^0.3.14
, which means 'any version that has a public API compatible with version 0.3.14' - Now that we have an external dependency, Cargo fetches the latest versions of everything from the registry, which is a copy of data from Crates.io. Crates.io is where people in the Rust ecosystem post their open source Rust projects for others to use.
- After updating the registry, Cargo checks the
[dependencies]
section and downloads any crates you don’t have yet.
- When first run
cargo build
, Cargo figures out all the versions of the dependencies that fit the criteria and then writes them to the Cargo.lock file.
- Cargo provides a command
cargo update
, which will ignore the Cargo.lock file and figure out all the latest versions that fits in Cargo.toml. - Another Cargo.lock will be created if the update works.
- There's no need to remember which traits to use and which methods and functions to call from a crate.
-
cargo doc --opne
will help create a document that contains all APIs from the crates specified in Cargo.toml
use rand::Rng;
use std::io;
fn main() {
println!("Guess the number!");
let secret_num = rand::thread_rng().gen_range(1, 100);
println!("The secret number is: {}", secret_num);
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {}", guess);
}
-
rand::thread_rng()
returns a particular randon number generator that is local to the current thread of excution and seeded by the OS. -
gen_range(start, end)
returns a random integer that in [start, end).
use rand::Rng;
use std::cmp::Ordering;
use std::io;
fn main() {
println!("Guess the number!");
let secret_num = rand::thread_rng().gen_range(1, 100);
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => {
println!("Input a number please.");
continue;
}
};
println!("You guessed: {}", guess);
match guess.cmp(&secret_num) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big"),
Ordering::Equal => {
println!("You win");
break;
}
}
}
}
- A
match
expression is made up of arms. An arm consists of a pattern and the code that should be run if the value given to the beginning of thematch
expression fits that arm's pattern. - Number in Rust defaults to an
i32
, which is also the type ofsecret_num
. So we should parse the variableguess
from String to integer (i32
,u32
,i64
,u64
, etc.) first. - Rust allows us to shadow the previous value of guess with a new one. This feature is used in situations in which a value should be converted from one type to another type.
- In this case, we convert the variable
guess
from String to integer without create another unique variables. - The colon(
:
) afterguess
tells Rust the variable's type. - If the
parse
is not able to turn the string into a number, it will return anErr
value that contains mor information about the error, which will match the patternErr(_)
.- The underscore,
_
, is a catchall value.
- The underscore,
- In Rust, the compiler guarantees that when you state that a value won’t change, it really won’t change.
- Variables are immutable only by default;
mut
can be annotated to allow this variable to be changed.
- Being unable to change the value of a variable is similar to another concept that known as constants. But there are a few differences between constants and variables.
- First, a constant cannot be annotated with
mnt
, it is always immutable. - A constant is declared with keyword
const
instead oflet
, and the type of the value must be annotated. - Constants can be declared in any scope, including the global scope.
- Constants may be set only to a constant expression, not the result of a function call or any other value that could only be computed at runtime.
- As is shown in Comparing the Guess to the Secret Number, a new variable can be declared with the same name as a previous variable, and the tnew one shadows the previous one.
- Shadowing is different from marking a variable as
mut
, because a compile-time error will occurs if we accidentally try to reassign to this variable without usinglet
keyword. - The other difference between
mut
and shadowing is that a brand new variable is created whenlet
is used and the type of the variable can be changed.
-
A scalar type represents a single value
-
Rust has for primary scalar types: integers, floating-point numbers, Booleans, and characters.
Length | Signed | Unsigned |
---|---|---|
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
128-bit | i128 | u128 |
arch (32 for 32-bits OS, and 64 for 64-bits OS) | isize | usize |
Number literals | Example |
---|---|
Decimal | 98_222 |
Hex | 0xff |
Octal | 0o77 |
Binary | 0b1111_0000 |
Byte | b'A' |
Length | Types |
---|---|
32-bit(float) | f32 |
64-bit(double) | f64 |
-
a Boolean type in Rust has two possible values:
true
andflase
. -
Booleans are one byte in size. Specified using
bool
- Rust's
char
type is the language's most primitive alphabetic type. -
char
literals are specified with single quotes, as opposed to string literals, which use double. - Rust's
char
type is four bytes in size, representing a Unicode Scalar Value.
- Compound types can group multiple values into one type.
- Rust has two primitive compound types: tuples and arrays.
- A
tuple
has fixed length: once declared, it cannot grow or shrink in size. - A tuple is created like following:
let tup: (i32, f64, u8) = (600, 1.1, 243);
- Pattern matching is used to destructure a tuple value, like following:
let (x, y, z) = tup; // x = 600, y = 1.1, z = 243
- Access to tuple elements needs a period
.
, like following:let x1 = tup.0; // x1 = 600
- Every element of an array must have the same type.
- Arrays in Rust have a fixed length, like tuples
- An array is created like following:
let a = [1, 2, 3, 4, 5];
let b = ["12", "34", "56"];
let c: [i32, 5] = [1, 2, 3, 4, 5];
let d = [3; 5]; // [3, 3, 3, 3, 3]
- Acess to array elements needs a bracket
[]
, like following:let first = a[0];
- A variable in the bracket is also allowed:
let one_element = a[index]
- Rust defines a function with keyword
fn
fn main() {
println!("Hello, world!");
}
- Functions can take parameters, which should be specified its type while defining.
fn foo(x: i32) {
println!("x is {}", x);
}
-
The return value is declared after an arrow(
->
) -
A
return
can be used anywhere in a function to return the value. -
But most functions return the last expression implicitly.
fn five() -> i32 {
5
}
fn six() -> i32 {
return 6;
}
- Especially, if the last line of the function is a statement rather than expression, an empty tupe will be returned.
fn foo() -> i32 {
6;
}
error[E0308]: mismatched types
--> src/main.rs:5:13
|
5 | fn foo() -> i32 {
| --- ^^^ expected i32, found ()
| |
| implicitly returns `()` as its body has no tail or `return` expression
6 | 6;
| - help: consider removing this semicolon
|
= note: expected type `i32`
found type `()`
fn main() {
let number = 3;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
}
-
Blocks of code associated with the conditions in
if
expressions are sometimes calledarm
s, just like the arms inmatch
expressions. -
The condition in
if
expressions must be abool
.
- Just like the other programming language
fn main() {
let condition = true;
let number = if condition {
5
} else {
6
};
println!("The value of number is: {}", number);
}
-
Like the ternary expression
?:
in C/CPP -
the two operands must be in the same type
- Rust has three kinds of loops:
loop
,while
,for
-
The
loop
keyword just goes into an infinite loop to execute a block until abreak
-
The
break
keyword can return values from a loop
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {}", result);
}
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
println!("LIFTOFF!!!");
}
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a.iter() {
println!("the value is: {}", element);
}
}
fn main() {
for number in (1..4).rev() {
println!("{}!", number);
}
println!("LIFTOFF!!!");
}
- Ownership enables Rust to make memory safety guarantees without needing a garbage collecto (aka GC).
- Memory is managed through a system of ownership with a set of rules that the compiler checks at compile time. It won't slow down the program.
- Each value in Rust has a variable that’s called its owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value will be dropped.
let s1 = String::from("hello");
let s2 = s1;
- To ensure memory safety, there’s one more detail to what happens in this situation in Rust. Instead of trying to copy the allocated memory, Rust considers
s1
to no longer be valid and, therefore, Rust doesn’t need to free anything whens1
goes out of scope. Check out what happens when you try to uses1
afters2
is created; it won’t work:
println!("{}, world!", s1);
error[E0382]: use of moved value: `s1`
--> src/main.rs:5:28
|
3 | let s2 = s1;
| -- value moved here
4 |
5 | println!("{}, world!", s1);
| ^^ value used here after move
|
= note: move occurs because `s1` has type `std::string::String`, which does
not implement the `Copy` trait
- Simply like the deep clone.
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
-
Rust has a special annotation called the
Copy
trait that we can place on types like integers that are stored on the stack. If a type has theCopy
trait, an older variable is still usable after assignment. Rust won’t let us annotate a type with theCopy
trait if the type, or any of its parts, has implemented theDrop
trait. If the type needs something special to happen when the value goes out of scope and we add theCopy
annotation to that type, we’ll get a compile-time error. -
Types with
Copy
trait: simply think all groups of simple scalar values can beCopy
- All the integer types, such as u32.
- The Boolean type, bool, with values true and false.
- All the floating point types, such as f64.
- The character type, char.
- Tuples, if they only contain types that are also Copy. For example, (i32, i32) is Copy, but (i32, String) is not.
- Passing a variable to a function will move or copy, just as assignment does.
fn main() {
let s = String::from("hello"); // s comes into scope
takes_ownership(s); // s's value moves into the function...
// ... and so is no longer valid here
let x = 5; // x comes into scope
makes_copy(x); // x would move into the function,
// but i32 is Copy, so it’s okay to still
// use x afterward
} // Here, x goes out of scope, then s. But because s's value was moved, nothing
// special happens.
fn takes_ownership(some_string: String) { // some_string comes into scope
println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called. The backing
// memory is freed.
fn makes_copy(some_integer: i32) { // some_integer comes into scope
println!("{}", some_integer);
} // Here, some_integer goes out of scope. Nothing special happens.
- Here
s
cannot be use after takes_ownership is called
- Return values can also transfer ownership.
fn main() {
let s1 = gives_ownership(); // gives_ownership moves its return
// value into s1
let s2 = String::from("hello"); // s2 comes into scope
let s3 = takes_and_gives_back(s2); // s2 is moved into
// takes_and_gives_back, which also
// moves its return value into s3
} // Here, s3 goes out of scope and is dropped. s2 goes out of scope but was
// moved, so nothing happens. s1 goes out of scope and is dropped.
fn gives_ownership() -> String { // gives_ownership will move its
// return value into the function
// that calls it
let some_string = String::from("hello"); // some_string comes into scope
some_string // some_string is returned and
// moves out to the calling
// function
}
// takes_and_gives_back will take a String and return one
fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
// scope
a_string // a_string is returned and moves out to the calling function
}
- Taking ownership and then returning ownership with every function is a bit tedious. In this case, references are using.
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
-
&s1
is passed intocalculate_length
and, in its definition,&String
is taken rather thanString
-
These ampersands are references, which allow some one to refer to some value without taking ownership of it.
-
We don’t drop what the reference points to when it goes out of scope because we don’t have ownership.
-
If we want to change the value of reference,
&mut
must be declared
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
-
This way, the reference to s has be
mut
, so it can be changed in the functionchange
-
But you can have only one mutable reference to a particular piece of data in a particular scope. This code will fail:
-
These codes will fail:
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
error[E0499]: cannot borrow `s` as mutable more than once at a time
--> src/main.rs:5:14
|
4 | let r1 = &mut s;
| ------ first mutable borrow occurs here
5 | let r2 = &mut s;
| ^^^^^^ second mutable borrow occurs here
6 |
7 | println!("{}, {}", r1, r2);
| -- first borrow later used here
-
This restriction has earned Rust the benefit of preventing data races at compile time.
-
A similar rule exists for combining mutable and immutable references. We also cannot have a mutable reference while we have an immutable one.
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM
println!("{}, {}, and {}", r1, r2, r3);
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:6:14
|
4 | let r1 = &s; // no problem
| -- immutable borrow occurs here
5 | let r2 = &s; // no problem
6 | let r3 = &mut s; // BIG PROBLEM
| ^^^^^^ mutable borrow occurs here
7 |
8 | println!("{}, {}, and {}", r1, r2, r3);
| -- immutable borrow later used here
- But these codes do work, because a reference's scope starts from where it is introduced and continues through the last time that is used.
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
println!("{} and {}", r1, r2);
// r1 and r2 are no longer used after this point
let r3 = &mut s; // no problem
println!("{}", r3);
- There's no danging pointer in Rust!
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String { // dangle returns a reference to a String
let s = String::from("hello"); // s is a new String
&s // we return a reference to the String, s
} // Here, s goes out of scope, and is dropped. Its memory goes away.
// Danger!
- At any given time, you can have either one mutable reference or any number of immutable references.
- References must always be valid.
-
Another data type that does not have ownership is the slice.
-
Slices let you reference a contiguous sequence of elements in a collection rather than the whole collection.
- The type that signifies "string slice" is write as
&str
fn main() {
let s = String::from("hello world");
let hello = &s[0..5];
let hello1 = &s[..5];
let world = &s[6..11];
let world1 = &s[6..];
let hello_world = &s[..];
let 1st_world = first_world(&s);
}
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
- Also It's impossible to keep the slice of a string while clearing the string itself.
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);
s.clear(); // error!
// clear is declared as :
// fn clear(&mut self) {
// self.vec.clear()
// }
println!("the first word is: {}", word);
}
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:18:5
|
16 | let word = first_word(&s);
| -- immutable borrow occurs here
17 |
18 | s.clear(); // error!
| ^^^^^^^^^ mutable borrow occurs here
19 |
20 | println!("the first word is: {}", word);
| ---- immutable borrow later used here
-
String literals being stored inside the binary, rather than alloced in the heap.
-
The type of string literals are
&str
. It also explains why the string literals cannot be modified
-
fn first_word(s: &String) -> &str {
is better to be written asfn first_word(s: &str) -> &str {
-
If we have a string slice, we can pass it directly. If we have a
String
, we can pass a slice of the entireString
. This will make our API more general and useful.
- The concepts of ownership, borrowing, and slices ensure memory safety in Rust programs at compile time.
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
fn main() {
let mut user1 = User {
email: String::from("[email protected]"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
user1.email = String::from("[email protected]");
}
- Note that the entire instance must be mutable -- Rust doesn't allow us to mark only sertain fields as mutable.
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
let user2 = User {
email: String::from("[email protected]"),
username: String::from("anotherusername567"),
..user1
};
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
-
You can also define structs that don’t have any fields! These are called unit-like structs because they behave similarly to
()
, the unit type. -
Unit-like structs can be useful in situations in which you need to implement a trait on some type but don’t have any data that you want to store in the type itself.
-
In the
User
struct definition, we used the owned String type rather than the&str
string slice type. This is a deliberate choice because we want instances of this struct to own all of its data and for that data to be valid for as long as the entire struct is valid. -
In Generic Types, Traits, and Lifetimes we will fix these errors with the lifetime feature.
-
Methods are similar to functions, but methods are always defined within the context of a struct(or enum, tarit object), and the first of their parameter is always
self
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
-
In C and C++, two different operators are used for calling methods: you use
.
if you’re calling a method on the object directly and->
if you’re calling the method on a pointer to the object and need to dereference the pointer first. In other words, ifobject
is a pointer,object->something()
is similar to(*object).something()
. -
Rust has a feature called automatic referencing and dereferencing instead of
->
operator -
when you call a method with object.something() , Rust automatically adds in
&
,&mut
, or*
so object matches the signature of the method, so the following codes are the same: `p1.distance(&p2); (&p1).distance(&p2); -
The fact that Rust makes borrowing implicit for method receivers is a big part of making ownership ergonomic in practice.
-
We're allowed to define functions within
impl
blocks that don't takeself
as a parameter -
That is called associated functions (aka, static functions).
::
is used to call them.
- Rust's enums are most similar to algebraic data types in functional languages
enum IpAddrKind {
v4,
v6,
}
struct IpAddr {
IpAddrKind,
address: String,
}
fn main() {
let home = IpAddr {
kind: IpAddrKind::V4,
address: String::from("127.0.0.1"),
}
}
enum IpAddr {
v4(u8, u8, u8, u8),
v6(String),
}
impl IpAddr {
fn disp(&self) {
println!("Called here");
}
}
fn main() {
let home = IpAddr::v4(127, 0, 0, 1);
let loopback: IpAddr = IpAddr::v6(String::from("::1"));
home.disp();
}
-
The
Option
type is used in many places because it encodes the very common scenario in which a value could be something or it could be nothing. -
In languages with null, variables can always be in one of two states: null or not-null. But Rust doesn't have athe null feature.
-
Rust uses
Option
s instead.
enum Option<T> {
Some(T),
None,
}
#![allow(unused_variables)]
fn main() {
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
}
- The
Option<T>
enum is so useful that it’s even included in the prelude; you don’t need to bring it into scope explicitly. In addition, so are its variants: you can useSome
andNone
directly without the Option:: prefix. TheOption<T>
enum is still just a regular enum, andSome(T)
andNone
are still variants of typeOption<T>
.
-
Rust has an extremely powerful control flow oeprator called
match
that allows you to comare a value against a series of patterns and then execute code based on matches. -
Patterns can be made up of literal values, variable names, wildcards, and many other thins, see Patterns and Matching
#[derive(Debug)] // so we can inspect the state in a minute
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
},
}
}
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
let some_u8_value = 0u8;
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (),
}
- The
if let
syntax lets you combineif
andlet
into a less verbose way to handle values that match one pattern while ignoring the rest.
let some_u8_value = Some(0u8);
match some_u8_value {
Some(3) => println!("three"),
_ => (),
}
if let Some(3) = some_u8_value {
println!("three");
}
let mut count = 0;
match coin {
Coin::Quarter(state) => println!("State quarter from {:?}!", state),
_ => count += 1,
}
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {:?}!", state);
} else {
count += 1;
}
-Packages: A Cargo feature that lets you build, test, and share crates -Crates: A tree of modules that produces a library or executable -Modules and use: Let you control the organization, scope, and privacy of paths -Paths: A way of naming an item, such as a struct, function, or module
- A crate is a binary of library.
- The crate root is a source file that the Rust compiler starts from and makes up the root module of this crate section
- A package is one or more crates that provide a set of functionality.
- A package contains a Cargo.toml file that describe s how to build those crates
- Cargo follows a convention that
src/main.rs
is the crate root of a binary crate with the same name as the package. - Likewise, Cargo knows that if the package directory contains
src/lib.rs
, the package contains a library crate with the same name as the package, andsrc/lib.rs
is its crate root.
- Modules let us organize code within a crate into groupes.
- Modules also control the privacy of items, which is whether an item can be used by outside code(public) or is an internal implementation detail and not available for outside use(private)
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
The module tree:
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
- A path can take two forms:
- An absolute path starts from a crate root by using a crate name or a literal
crate
- A relative path starts from the current module and uses
self
,super
or an identifier in the current module.
- An absolute path starts from a crate root by using a crate name or a literal
- Both of them are followed by one or more identifiers separated by
::
- Our preference is to specify absolute paths because it’s more likely to move code definitions and item calls independently of each other.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
fn serve_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::serve_order();
}
fn cook_order() {}
}
- Structs
mod back_of_house {
pub struct Breakfast {
pub toast: String,
seasonal_fruit: String,
}
impl Breakfast {
pub fn summer(toast: &str) -> Breakfast {
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("peaches"),
}
}
}
}
pub fn eat_at_restaurant() {
// Order a breakfast in the summer with Rye toast
let mut meal = back_of_house::Breakfast::summer("Rye");
// Change our mind about what bread we'd like
meal.toast = String::from("Wheat");
println!("I'd like {} toast please", meal.toast);
// The next line won't compile if we uncomment it; we're not allowed
// to see or modify the seasonal fruit that comes with the meal
// meal.seasonal_fruit = String::from("blueberries");
}
- Enum
mod back_of_house {
pub enum Appetizer {
Soup,
Salad,
}
}
pub fn eat_at_restaurant() {
let order1 = back_of_house::Appetizer::Soup;
let order2 = back_of_house::Appetizer::Salad;
}
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
use front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
}
fn function2() -> IoResult<()> {
// --snip--
}
- When we bring a name into scope with the
use
keyword, the name available in the new scope is private. To enable the code that calls our code to refer to that name as if it had been defined in that code’s scope, we can combinepub
anduse
. This technique is called re-exporting because we’re bringing an item into scope but also making that item available for others to bring into their scope.
use std::{cmp::Ordering, io};
use std::io::{self, Write};
use std::collections::*;
- Unlike the built-in array and tuple types, the data collections point to is stored on the heap.
let mut v: Vec<i32> = Vec::new();
let mut v = vec![1, 2, 3];
v.push(4);
v.push(5);
let third: &i32 = &v[2];
match v.get(2) {
Some(third) => println!("The third elements is {}", third),
None => println!("There is no third element."),
}
for i in &v {
println!("{}", i);
}
for i in &mut v{
*i += 50; // To change the value that the mutable reference refers to, we have to use the dereference operator (*)
}
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
- When the program has a valid reference, the borrow checker enforces the ownership and borrowing rules to ensure this reference and any other references to the contents of the vector remain valid. Recall the rule that states you can't have mutable and immutable references in the same scope.
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6);
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
--> src/main.rs:6:5
|
4 | let first = &v[0];
| - immutable borrow occurs here
5 |
6 | v.push(6);
| ^^^^^^^^^ mutable borrow occurs here
7 |
8 | println!("The first element is: {}", first);
| ----- immutable borrow later used here
let hello = String::from("السلام عليكم");
let hello = String::from("Dobrý den");
let hello = String::from("Hello");
let hello = String::from("שָׁלוֹם");
let hello = String::from("नमस्ते");
let hello = String::from("こんにちは");
let hello = String::from("안녕하세요");
let hello = String::from("你好");
let hello = String::from("Olá");
let hello = String::from("Здравствуйте");
let hello = String::from("Hola");
let mut s = String::from("foo");
s.push_str("bar");
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // note s1 has been moved here and can no longer be used
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{}-{}-{}", s1, s2, s3);
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
use std::collections::HashMap;
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();
use std::collections::HashMap;
let field_name = String::from("Favorite color");
let field_value = String::from("Blue");
let mut map = HashMap::new();
map.insert(field_name, field_value);
// field_name and field_value are invalid at this point
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
let team_name = String::from("Blue");
let score = scores.get(&team_name);
for (key, value) in &scores {
println!("{}: {}", key, value);
}
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
// Overwriting
scores.insert(String::from("Blue"), 25);
// insert if key has no value
scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50); // do nothing
// based on the old
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
-
Errors are grouped into two major categories: recoverable and unrecoverable
-
Rust has the type
Result<T, E>
for recoverable errors and thepanic!
macro that stops execution when the program encounters an unrecoverable error.
fn main() {
panic!("crash and burn");
}
- To switch from unwinding the stack to just aborting, add `[profile.release] \n panic = 'abort' to Cargo.toml
enum Result<T, E> {
Ok(T),
Err(E),
}
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {:?}", e),
},
other_error => panic!("Problem opening the file: {:?}", other_error),
},
};
}
- There's a more clear writting using
closure
-
unwrap
is a shortcut method:
use std::fs::File;
fn main() {
let f = File::open("hello.txt").unwrap();
}
// ---------- the same as ----------
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => {
panic!(error)
},
};
}
-
except
used in the same way asunwarp
, but we can pass arguments to theexcept
to pass topanic!
.
-
It's better to return an error in a function rather than just handle it, since we don't have enough information on what the calling code is actually trying to do.
-
Use
?
operator as a shortcut for propagating errors.
use std::io;
use std::io::Read;
use std::fs::File;
fn read_username_from_file() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}
// ------- Use API
use std::io;
use std::fs;
fn read_username_from_file() -> Result<String, io::Error> {
fs::read_to_string("hello.txt")
}
-
NOTE, the
?
operator can only be used in functions that returnResult
-
It's appropriate to panic in examples, prototype code, and tests
-
Cases in which you have more information than the compiler: It would also be appropriate to call
unwrap
when you have some other logic that ensures the Result will have anOk
value, but the logic isn’t something the compiler understands.
-
It’s advisable to have your code panic when it’s possible that your code could end up in a bad state. In this context, a bad state is when some assumption, guarantee, contract, or invariant has been broken, such as when invalid values, contradictory values, or missing values are passed to your code—plus one or more of the following:
- The bad state is not something that’s expected to happen occasionally.
- Your code after this point needs to rely on not being in this bad state.
- There’s not a good way to encode this information in the types you use.
-
If someone calls your code and passes in values that don’t make sense, the best choice might be to call
panic!
and alert the person using your library to the bug in their code so they can fix it during development. Similarly,panic!
is often appropriate if you're calling external code that is out of your control and it returns an invalid state that you have no way of fixing. -
However, when failure is expected, it's more appropriate to return a
Result
than to make apanic!
call. -
When your code performs operations on values, your code should verify the values are valid first and panic if the values aren’t valid. This is mostly for safety reasons: attempting to operate on invalid data can expose your code to vulnerabilities.
struct Point<T> {
x: T,
y: T,
}
fn main() {
let integer = Point { x: 5, y: 10 };
let float = Point { x: 1.0, y: 4.0 };
}
// -----------------------
struct Point<T, U> {
x: T,
y: U,
}
fn main() {
let both_integer = Point { x: 5, y: 10 };
let both_float = Point { x: 1.0, y: 4.0 };
let integer_and_float = Point { x: 5, y: 4.0 };
}
enum Result<T, E> {
Ok(T),
Err(E),
}
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
fn main() {
let p = Point { x: 5, y: 10 };
println!("p.x = {}", p.x());
}
struct Point<T, U> {
x: T,
y: U,
}
impl<T, U> Point<T, U> {
fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
Point {
x: self.x,
y: other.y,
}
}
}
fn main() {
let p1 = Point { x: 5, y: 10.4 };
let p2 = Point { x: "Hello", y: 'c'};
let p3 = p1.mixup(p2);
println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}
- Traits are similar to a feature often called interfaces in other languages, although with some differences.
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub fn notify(item: impl Summary) {
println!("Breaking news! {}", item.summarize());
}
pub fn notify<T: Summary>(item: T) {
println!("Breaking news! {}", item.summarize());
}
pub fn notify(item: impl Summary + Display) { }
pub fn notify<T: Summary + Display>(item: T) { }
fn some_function<T, U>(t: T, u: U) -> i32
where T: Display + Clone,
U: Clone + Debug
{ }
fn returns_summarizable() -> impl Summary {}
use std::fmt::Display;
struct Pair<T> {
x: T,
y: T,
}
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self {
x,
y,
}
}
}
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {}", self.x);
} else {
println!("The largest member is y = {}", self.y);
}
}
}
-
The type
Pair<T>
always implements thenew
function. ButPair<T>
only implements thecmp_display
method if its inner typeT
implements thePartialOrd
trait that enables comparison and theDisplay
trait that enables printing. -
We can also conditionally implement a trait for any type that implements another trait. Implementations of a trait on any type that satisfies the trait bounds are called blanket implementations
impl<T: Display> ToString for T {
// --snip--
}
{
let r; // ---------+-- 'a
// |
{ // |
let x = 5; // -+-- 'b |
r = &x; // | |
} // -+ |
// |
println!("r: {}", r); // |
} // ---------+
- The program is rejected because
'b
is shorter than'a
: the subject of the reference doesn't live as long as the reference
{
let x = 5; // ----------+-- 'b
// |
let r = &x; // --+-- 'a |
// | |
println!("r: {}", r); // | |
// --+ |
} // ----------+
- Here,
x
has the lifetime'b
, which is in this case larger than'a
&i32 // a reference
&'a i32 // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
-
The function signature tells Rust that the string slice returned from the function will live at least as long as lifetime
'a
. -
In practice, it means that the lifetime of the reference returned by the
longest
function is the same as the smaller of the lifetimes of the references passed in.
struct ImportantExcerpt<'a> {
part: &'a str,
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.')
.next()
.expect("Could not find a '.'");
let i = ImportantExcerpt { part: first_sentence };
}
- This annotation means an instance of
ImportantExcerpt
can’t outlive the reference it holds in itspart
field.
impl<'a> ImportantExcerpt<'a> {
fn level(&self) -> i32 {
3
}
}
- Lifetime names for struct fields always need to be declared after the
impl
keyword and then used after the struct’s name, because those lifetimes are part of the struct’s type.
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
- The compiler uses three rules to figure out what lifetimes references have when there aren’t explicit annotations.
- each parameter that is a reference gets its own lifetime parameter.
- if there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters
- if there are multiple input lifetime parameters, but one of them is
&self
or&mut self
because this is a method, the lifetime ofself
is assigned to all output lifetime parameters.
let s: &'static str = "I have a static lifetime.";
use std::fmt::Display;
fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
where T: Display
{
println!("Announcement! {}", ann);
if x.len() > y.len() {
x
} else {
y
}
}
- Set up needed data or state
- Run the code you want to test
- Assert the results are what you expected
cargo new adder --lib
#[cfg(test)]
mod tests {
#[test]
fn exploration() {
assert_eq!(2 + 2, 4);
}
#[test]
fn another() {
panic!("Make this test fail");
}
}
cargo test
running 2 tests
test tests::exploration ... ok
test tests::another ... FAILED
failures:
---- tests::another stdout ----
thread 'tests::another' panicked at 'Make this test fail', src/lib.rs:10:9
note: Run with `RUST_BACKTRACE=1` for a backtrace.
failures:
tests::another
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
error: test failed
- Checking for Panics with
should_panic
pub struct Guess {
value: i32,
}
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 || value > 100 {
panic!("Guess value must be between 1 and 100, got {}.", value);
}
Guess {
value
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn greater_than_100() {
Guess::new(200);
}
}
// -----cargo test
running 1 test
test tests::greater_than_100 ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
- Using
Result<T, E>
in Tests
#[cfg(test)]
mod tests {
#[test]
fn it_works() -> Result<(), String> {
if 2 + 2 == 4 {
Ok(())
} else {
Err(String::from("two plus two does not equal four"))
}
}
}
// ----- cargo test
running 1 test
test tests::greater_than_100 ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
- The attribute
cfg
stands for configuration and tells Rust that the following item should only be included given a certain configuration option. In this case, the configuration option istest
, which is provided by Rust for compiling and running tests.
$cargo test -- --test-threads=1
fn prints_and_returns_10(a: i32) -> i32 {
println!("I got the value {}", a);
10
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn this_test_will_pass() {
let value = prints_and_returns_10(4);
assert_eq!(10, value);
}
#[test]
fn this_test_will_fail() {
let value = prints_and_returns_10(8);
assert_eq!(5, value);
}
}
// ------- cargo test
cargo test
running 2 tests
test tests::this_test_will_pass ... ok
test tests::this_test_will_fail ... FAILED
failures:
---- tests::this_test_will_fail stdout ----
I got the value 8
thread 'tests::this_test_will_fail' panicked at 'assertion failed: `(left == right)`
left: `5`,
right: `10`', src/lib.rs:19:9
note: Run with `RUST_BACKTRACE=1` for a backtrace.
failures:
tests::this_test_will_fail
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
// -------- cargo test -- --nocapture
cargo test -- --nocapture --test-threads=1
running 2 tests
test tests::this_test_will_fail ... I got the value 8
thread 'main' panicked at 'assertion failed: `(left == right)`
left: `5`,
right: `10`', src/lib.rs:19:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
FAILED
test tests::this_test_will_pass ... I got the value 4
ok
failures:
failures:
tests::this_test_will_fail
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
pub fn add_two(a: i32) -> i32 {
a + 2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_two_and_two() {
assert_eq!(4, add_two(2));
}
#[test]
fn add_three_and_two() {
assert_eq!(5, add_two(3));
}
#[test]
fn one_hundred() {
assert_eq!(102, add_two(100));
}
}
// ------- cargo test one_hundred
running 1 test
test tests::one_hundred ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out
// ------ cargo test add
running 2 tests
test tests::add_two_and_two ... ok
test tests::add_three_and_two ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
#[test]
#[ignore]
fn expensive_test() {
// code that takes an hour to run
}
- Integration tests are entirely external to your library
- Integration tests use your library in the same way any other code would, which means they can only call functions that are part of your library’s public API.
- The
tests
directory at the top level tells Cargo that it contains integration tests. - Each file in the
tests
directory is compiled as its own separate crate. - Files in subdirectories of the
tests
directory don’t get compiled as separate crates or have sections in the test output.
- The same as lambdas in python.
fn generate_workout(intensity: u32, random_number: u32) {
let expensive_closure = |num| {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
num
};
if intensity < 25 {
println!(
"Today, do {} pushups!",
expensive_closure(intensity)
);
println!(
"Next, do {} situps!",
expensive_closure(intensity)
);
} else {
if random_number == 3 {
println!("Take a break today! Remember to stay hydrated!");
} else {
println!(
"Today, run for {} minutes!",
expensive_closure(intensity)
);
}
}
}
-
Closures don’t require you to annotate the types of the parameters or the return value like
fn
functions do. -
But we can do this annotation explicitly
let expensive_closure = |num: u32| -> u32 {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
num
};
// a vertical comparison of the syntax for
// the definition of a function that adds 1 to its parameter
// and a closure that has the same behavior.
fn add_one_v1 (x: u32) -> u32 { x + 1 }
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x| { x + 1 };
let add_one_v4 = |x| x + 1 ;
-
All closures implement at least one of the traits:
Fn
,FnMut
, orFnOnce
. -
So we can add the closure to a struct and the implement a cache for it.
struct Cacher<T>
where
T: Fn(u32) -> u32,
{
calculation: T,
pub value: HashMap<u32, u32>,
}
impl<T> Cacher<T>
where
T: Fn(u32) -> u32,
{
fn new(calculation: T) -> Cacher<T> {
Cacher {
calculation,
value: HashMap::new(),
}
}
fn value(&mut self, arg: u32) -> u32 {
// to use or_insert api, the lambda function will be called first
// self.value.entry(arg).or_insert((self.calculation)(arg))
if let Entry::Vacant(_) = self.value.entry(arg) {
self.value.insert(arg, (self.calculation)(arg));
}
self.value[&arg]
}
}
fn generate_workout(intensity: u32, random_number: u32) {
let mut expensive_result = Cacher::new(|num| {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
num + 1
});
if intensity < 25 {
println!("Today, do {} pushups!", expensive_result.value(intensity));
println!("Next, do {} situps!", expensive_result.value(intensity));
} else {
if random_number == 3 {
println!("Take a break today! Remember to stay hydrated!");
} else {
println!(
"Today, run for {} minutes!",
expensive_result.value(intensity)
);
}
}
}
fn main() {
let x = 4;
let equal_to_x = |z| z == x;
let y = 4;
assert!(equal_to_x(y));
}
// -----------------------------
fn main() {
let x = 4;
fn equal_to_x(z: i32) -> bool { z == x }
let y = 4;
assert!(equal_to_x(y));
}
error[E0434]: can't capture dynamic environment in a fn item; use the || { ...
} closure form instead
--> src/main.rs
|
4 | fn equal_to_x(z: i32) -> bool { z == x }
|
-
Closures can capture values from their environment in three ways, which directly map to the three ways a function can take a parameter: taking ownership, borrowing mutably, and borrowing immutably. These are encoded in the three
Fn
traits as follows:-
FnOnce
: consumes the variables it captures from its enclosing scope, known as the closure’s environment. To consume the captured variables, the closure must take ownership of these variables and move them into the closure when it is defined. TheOnce
part of the name represents the fact that the closure can’t take ownership of the same variables more than once, so it can be called only once. -
FnMut
: can change the environment because it mutably borrows values. -
Fn
: borrows values from the environment immutably.
-
-
If you want to force the closure to take ownership of the values it uses in the environment, you can use the
move
keyword before the parameter list.
let v1 = vec![1, 2, 3];
// --------------------------
let v1_iter = v1.iter();
for val in v1_iter {
println!("Got: {}", val);
}
// --------------------------
let mut v1_iter = v1.iter();
assert_eq!(v1_iter.next(), Some(&1));
assert_eq!(v1_iter.next(), Some(&2));
assert_eq!(v1_iter.next(), Some(&3));
assert_eq!(v1_iter.next(), None);
// ---------------------------
let v1_iter = v1.iter();
let total: i32 = v1_iter.sum(); // ownership passed
assert_eq!(total, 6);
// ---------------------------
let v1: Vec<i32> = vec![1, 2, 3];
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); // the comsuming(collect) is necessary
assert_eq!(v2, vec![2, 3, 4]);
// ----------------------------
#[derive(PartialEq, Debug)]
struct Shoe {
size: u32,
style: String,
}
fn shoes_in_my_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
shoes.into_iter()
.filter(|s| s.size == shoe_size)
.collect()
}
#[test]
fn filters_by_size() {
let shoes = vec![
Shoe { size: 10, style: String::from("sneaker") },
Shoe { size: 13, style: String::from("sandal") },
Shoe { size: 10, style: String::from("boot") },
];
let in_my_size = shoes_in_my_size(shoes, 10); // ownership passed
assert_eq!(
in_my_size,
vec![
Shoe { size: 10, style: String::from("sneaker") },
Shoe { size: 10, style: String::from("boot") },
]
);
}
-
Conclusion first: The iterator version is slightly faster.
-
The point is this: iterators, although a high-level abstraction, get compiled down to roughly the same code as if you’d written the lower-level code yourself. Iterators are one of Rust’s zero-cost abstractions, by which we mean using the abstraction imposes no additional runtime overhead.
-
This is analogous to how Bjarne Stroustrup, the original designer and implementor of C++, defines zero-overhead in “Foundations of C++” (2012):
In general, C++ implementations obey the zero-overhead principle: What you don’t use, you don’t pay for. And further: What you do use, you couldn’t hand code any better.
- First, Distinguishing production and debug, with the optimize level.
[profile.dev]
opt-level = 0
[profile.release]
opt-level = 3
- Second, making useful doc commnet:
///
and the markdown.- follow the convention, sections should be list as
# Examples
-
# Panics
: The scenarios in which the function being documented could panic. Callers of the function who don’t want their programs to panic should make sure they don’t call the function in these situations. -
# Errors
: If the function returns aResult
, describing the kinds of errors that might occur and what conditions might cause those errors to be returned can be helpful to callers so they can write code to handle the different kinds of errors in different ways. -
# Safety
: If the function is unsafe to call, there should be a section explaining why the function is unsafe and covering the invariants that the function expects callers to uphold.
- use
//!
instead of///
to add doc to items which contains the them.
- follow the convention, sections should be list as
//! # My Crate
//!
//! `my_crate` is a collection of utilities to make performing certain
//! calculations more convenient.
// ------------------------------------
/// Adds one to the number given.
///
/// # Examples
///
/// ```Rust
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
x + 1
}
-
Third, using
pub use
to re-export the APIs -
Forth, setting up a crates.io Account:
- Before you can publish any crates, you need to create an account on crates.io and get an API token. To do so, visit the home page at crates.io and log in via a GitHub account. (The GitHub account is currently a requirement, but the site might support other ways of creating an account in the future.) Once you’re logged in, visit your account settings at profile and retrieve your API key. Then run the cargo login command with your API key, like this:
cargo login *****
- a login info will be stored @ ~/.cargo/credentials
- Before you can publish any crates, you need to create an account on crates.io and get an API token. To do so, visit the home page at crates.io and log in via a GitHub account. (The GitHub account is currently a requirement, but the site might support other ways of creating an account in the future.) Once you’re logged in, visit your account settings at profile and retrieve your API key. Then run the cargo login command with your API key, like this:
-
Fifth, adding metadata to a new crate
[package]
name = "********"
version = "0.1.0"
authors = ["Your Name <[email protected]>"]
edition = "2018"
description = ""
license = "****"
-
Sixth, publish it with :
cargo publish
-
To publish a new version of an existing crate, just change the
[version]
value and re-publish -
To remove versions from crates.io, use
cargo yank --vers a.b.c
- undo it:
cargo yank --vers a.b.c --undo
- undo it:
- A workspace is a set of packages that share the same Cargo.lock, and output directory.
$mkdir add
$cd add
$touch Cargo.toml
; ----------Cargo.toml
[workspace]
members = [
"adder",
"add-one",
]
; --------------------
$cargo new adder
$cargo new add-one --lib
; ----------tree
├── Cargo.lock
├── Cargo.toml
├── add-one
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
; --------------
; ---------adder/Cargo.toml
[dependencies]
add-one = { path = "../add-one" }
; -------------------------
$ cargo install ripgrep
Updating registry `https://github.com/rust-lang/crates.io-index`
Downloading ripgrep v0.3.2
--snip--
Compiling ripgrep v0.3.2
Finished release [optimized + debuginfo] target(s) in 97.91 secs
Installing ~/.cargo/bin/rg
- A difference between references and smart pointers is that references are pointers that only borrow data; in contrast, in many cases, smart pointers own the data they point to.
- Boxes don’t have performance overhead, other than storing their data on the heap instead of on the stack. But they don’t have many extra capabilities either. You’ll use them most often in these situations:
- When you have a type whose size can’t be known at compile time and you want to use a value of that type in a context that requires an exact size
- When you have a large amount of data and you want to transfer ownership but ensure the data won’t be copied when you do so
- When you want to own a value and you care only that it’s a type that implements a particular trait rather than being of a specific type
fn main() {
let b = Box::new(5);
println!("b = {}", b);
}
-
cons list
- A cons list is a data structure that comes from Lisp
- The
cons
function (short for “construct function”) constructs a new pair from its two arguments, which usually are a single value and another pair. These pairs containing pairs form a list. - The cons function concept has made its way into more general functional programming jargon: “to cons x onto y” informally means to construct a new container instance by putting the element x at the start of this new container, followed by the container y.
enum List {
Cons(i32, Box<List>),
Nil,
}
use crate::List::{Cons, Nil};
fn main() {
let list = Cons(1,
Box::new(Cons(2,
Box::new(Cons(3,
Box::new(Nil))))));
}
- Implementing the
Deref
trait allows you to customize the behavior of the dereference operator,*
(as opposed to the multiplication or glob operator).
use std::ops::Deref;
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
fn main() {
let x = 5;
let y = MyBox::new(x);
assert_eq!(5, x);
assert_eq!(5, *y);
}
-
The
type Target = T;
syntax defines an associated type for theDeref
trait to use. -
We fill in the body of the
deref
method with&self.0
so deref returns a reference to the value we want to access with the*
operator. -
interacts with mutability:
- Rust does deref coercion when it finds types and trait implementations in three cases:
- From &T to &U when T: Deref<Target=U>
- From &mut T to &mut U when T: DerefMut<Target=U>
- From &mut T to &U when T: Deref<Target=U>
- The first two cases are the same except for mutability.
- For the third case: Rust will also coerce a mutable reference to an immutable one. But the reverse is not possible.
- Rust does deref coercion when it finds types and trait implementations in three cases:
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
let c = CustomSmartPointer { data: String::from("my stuff") };
let d = CustomSmartPointer { data: String::from("other stuff") };
println!("CustomSmartPointers created.");
}
// ---------------------------------
CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!
Dropping CustomSmartPointer with data `my stuff`!
-
Rust doesn’t let you call the Drop trait’s drop method manually; instead you have to call the
std::mem::drop
function provided by the standard library if you want to force a value to be dropped before the end of its scope.- For example, drop a lock.
fn main() { let c = CustomSmartPointer { data: String::from("some data") }; println!("CustomSmartPointer created."); drop(c); println!("CustomSmartPointer dropped before the end of main."); }
- To enable multiple ownership,
Rc<T>
is used, which is an abbreviation for reference counting.- The
Rc<T>
type keeps track of the number of references to a value which determines whether or not a value is still in use. - If there are zero references to a value, the value can be cleaned up without any references becoming invalid.
- The
enum List {
Cons(i32, Rc<List>),
Nil,
}
use crate::List::{Cons, Nil};
use std::rc::Rc;
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
let b = Cons(3, Rc::clone(&a));
let c = Cons(4, Rc::clone(&a));
}
- The implementation of
Rc::clone
doesn’t make a deep copy of all the data like most types’ implementations ofclone
do.- The call to
Rc::clone
only increments the reference count, which doesn’t take much time. - We could have called
a.clone()
, but just useRc::clone
following the convention to make a difference
- The call to
-
Interior mutability is a design pattern in Rust that allows you to mutate data even when there are immutable references to that data; normally, this action is disallowed by the borrowing rules.
- To mutate data, the pattern uses
unsafe
code inside a data structure to bend Rust’s usual rules that govern mutation and borrowing. - The
unsafe
code involved is then wrapped in a safe API, and the outer type is still immutable.
- To mutate data, the pattern uses
- Unlike
Rc<T>
, theRefCell<T>
type represents single ownership over the data it holds.- With references and
Box<T>
, the borrowing rules’ invariants are enforced at compile time. WithRefCell<T>
, these invariants are enforced at runtime. - The advantage of checking the borrowing rules at runtime instead is that certain memory-safe scenarios are then allowed, whereas they are disallowed by the compile-time checks. Static analysis, like the Rust compiler, is inherently conservative.
- Similar to
Rc<T>
,RefCell<T>
is only for use in single-threaded scenarios and will give you a compile-time error if you try using it in a multithreaded context.
- With references and
-
Box<T>
,Rc<T>
, orRefCell<T>
:-
Rc<T>
enables multiple owners of the same data;Box<T>
andRefCell<T>
have single owners. -
Box<T>
allows immutable or mutable borrows checked at compile time;Rc<T>
allows only immutable borrows checked at compile time;RefCell<T>
allows immutable or mutable borrows checked at runtime. - Because
RefCell<T>
allows mutable borrows checked at runtime, you can mutate the value inside theRefCell<T>
even when theRefCell<T>
is immutable.
-
- A test double is the general programming concept for a type used in place of another type during testing. Mock objects are specific types of test doubles that record what happens during a test so you can assert that the correct actions took place.
#[derive(Debug)]
enum List {
Cons(Rc<RefCell<i32>>, Rc<List>),
Nil,
}
use crate::List::{Cons, Nil};
use std::rc::Rc;
use std::cell::RefCell;
fn main() {
let value = Rc::new(RefCell::new(5));
let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a));
let c = Cons(Rc::new(RefCell::new(10)), Rc::clone(&a));
*value.borrow_mut() += 10;
println!("a after = {:?}", a);
println!("b after = {:?}", b);
println!("c after = {:?}", c);
}
- Rust’s memory safety guarantees make it difficult, but not impossible, to accidentally create memory that is never cleaned up (aka memory leak).
use std::rc::Rc;
use std::cell::RefCell;
use crate::List::{Cons, Nil};
#[derive(Debug)]
enum List {
Cons(i32, RefCell<Rc<List>>),
Nil,
}
impl List {
fn tail(&self) -> Option<&RefCell<Rc<List>>> {
match self {
Cons(_, item) => Some(item),
Nil => None,
}
}
}
fn main() {
let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
println!("a initial rc count = {}", Rc::strong_count(&a));
println!("a next item = {:?}", a.tail());
let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
println!("a rc count after b creation = {}", Rc::strong_count(&a));
println!("b initial rc count = {}", Rc::strong_count(&b));
println!("b next item = {:?}", b.tail());
if let Some(link) = a.tail() {
*link.borrow_mut() = Rc::clone(&b);
}
println!("b rc count after changing a = {}", Rc::strong_count(&b));
println!("a rc count after changing a = {}", Rc::strong_count(&a));
// Uncomment the next line to see that we have a cycle;
// it will overflow the stack
println!("a next item = {:?}", a.tail());
}
// -------------------------------
a initial rc count = 1
a next item = Some(RefCell { value: Nil })
a rc count after b creation = 2
b initial rc count = 1
b next item = Some(RefCell { value: Cons(5, RefCell { value: Nil }) })
b rc count after changing a = 2
a rc count after changing a = 2
-
Turning
Rc<T>
into aWeak<T>
, which is cleaned up with no guarantee that theweak_count
is zero by callingRc::downgrade
. -
Strong references are how you can share ownership of an
Rc<T>
instance. Weak references don’t express an ownership relationship. They won’t cause a reference cycle because any cycle involving some weak references will be broken once the strong reference count of values involved is 0. -
Use
upgrade
on aWeak<T>
to ensure that the reference not dropped.
- Handling concurrent programming safely and efficiently is another of Rust’s major goals.
-
Rust standard library only provides an implementation of 1:1 threading (which means, one operating system thread per one language thread)
-
Creating a New Thread with
spawn
use std::thread;
use std::time::Duration;
fn main() {
thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
}
- Waiting Thread with
join
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
handle.join().unwrap();
}
- IPC with channel
use std::thread;
use std::sync::mpsc;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap(); // pass the ownership
});
let received = rx.recv().unwrap();
println!("Got: {}", received);
}
// ----------------------------
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let vals = vec![
String::from("hi"),
String::from("from"),
String::from("the"),
String::from("thread"),
];
for val in vals {
tx.send(val).unwrap();
thread::sleep(Duration::from_secs(1));
}
});
for received in rx {
println!("Got: {}", received);
}
}
- IPC with
Mutex<T>
use std::sync::Mutex;
fn main() {
let m = Mutex::new(5);
{
let mut num = m.lock().unwrap();
*num = 6;
}
println!("m = {:?}", m);
}
-
Mutex<T>
cannot be store in aRc<T>
and passed to different threads as it is unsafe. -
Arc<T>
is a type likeRc<T>
that is safe to use as it is atomic.
use std::sync::{Mutex, Arc};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0)); // cannot use Rc here
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
- Transfer ownership between threads with
Send
- The
Send
marker trait indicates that ownership of the type implementingSend
can be transferred between threads. - Almost every Rust type is Send, but there are some exceptions, including
Rc<T>
- The
- Access from multiple threads with
Sync
- The
Sync
marker trait indicates that it is safe for the type implementingSync
to be referenced from multiple threads. - If some type
T
have its reference&T
beSend
, theT
should beSync
-
Rc<T>
is notSync
,RefCell<T>
and its familyCell<T>
neither.
- The
- Types made up of
Send
andSync
are alsoSend
andSync
- do not implement those trait manuallay.
-
Object-oriented programs are made up of objects. An object packages both data and the procedures that operate on that data.
-
If a language must have inheritance to be an object-oriented language, then Rust is not one. There is no way to define a struct that inherits the parent struct’s fields and method implementations.
-
You can share Rust code using default trait method implementations instead.
- Trait objects are more like objects in other languages in the sense that they combine data and behavior. But trait objects differ from traditional objects in that we can’t add data to a trait object.
-
Object Safety Is Required for Trait Objects
- The return type isn’t
Self
. - There are no generic type parameters.
- The return type isn’t
if let
fn main() {
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();
if let Some(color) = favorite_color {
println!("Using your favorite color, {}, as the background", color);
} else if is_tuesday {
println!("Tuesday is green day!");
} else if let Ok(age) = age {
if age > 30 {
println!("Using purple as the background color");
} else {
println!("Using orange as the background color");
}
} else {
println!("Using blue as the background color");
}
}
while let
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("{}", top);
}
-
Patterns come in two forms: refutable and irrefutable.
- Patterns that will match for any possible value passed are irrefutable.
- Patterns that can fail to match for some possible value are refutable.
-
Function parameters,
let
statements, andfor
loops can only accept irrefutable patterns, because the program cannot do anything meaningful when values don’t match. -
The
if let
andwhile let
expressions only accept refutable patterns, because by definition they’re intended to handle possible failure: the functionality of a conditional is in its ability to perform differently depending on success or failure.
let x = 1;
match x {
Some(0) => println!("Got 0"),
1 | 2 => println!("one or two"),
3 => println!("three"),
4..=7 => println!("four through seven"),
_ => println!("anything"),
}
// -------------------------------
struct Point {
x: i32,
y: i32,
}
let p = Point { x: 0, y: 7 };
match p {
Point { x, y: 0 } => println!("On the x axis at {}", x),
Point { x: 0, y } => println!("On the y axis at {}", y),
Point { x, y } => println!("On neither axis: ({}, {})", x, y),
}
// -------------------------------
let x = 4;
let y = false;
match x {
4 | 5 | 6 if y => println!("yes"),
_ => println!("no"),
}
// -------------------------------
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, .., last) => {
println!("Some numbers: {}, {}", first, last);
},
}
// -------------------------------
enum Message {
Hello { id: i32 },
}
let msg = Message::Hello { id: 5 };
match msg {
Message::Hello { id: id_variable @ 3..=7 } => {
println!("Found an id in range: {}", id_variable)
},
Message::Hello { id: 10..=12 } => {
println!("Found an id in another range")
},
Message::Hello { id } => {
println!("Found some other id: {}", id)
},
}
-
Unsafe Rust exists because, by nature, static analysis is conservative.
-
To switch to unsafe Rust, use the
unsafe
keyword and then start a new block that holds the unsafe code. You can take four actions in unsafe Rust, called unsafe superpowers, that you can’t in safe Rust. Those superpowers include the ability to:- Dereference a raw pointer
- Call an unsafe function or method
- Access or modify a mutable static variable
- Implement an unsafe trait
- Access fields of
union
s
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
unsafe {
println!("r1 is: {}", *r1);
println!("r2 is: {}", *r2);
}
let address = 0x012345usize;
let r3 = address as *const i32;
let r4 = address as *mut i32;
let slice: &[i32] = unsafe {
slice::from_raw_parts_mut(r4, 10000)
};
extern "C" {
fn abs(input: i32) -> i32;
}
unsafe {
println!("Absolute value of -3 according to C: {}", abs(-3));
}
// on the contrary, extern a c function
#[no_mangle]
pub extern "C" fn call_from_c() {
println!("Just called a Rust function from C!");
}
// change a const variable
static mut COUNTER: u32 = 0;
fn add_to_count(inc: u32) {
unsafe {
COUNTER += inc;
}
}
- Equivalent Syntax in C language
const int num1 = 5;
int num2 = 5;
uintptr_t address = 0x012345;
int &r1 = num1;
int &r2 = num2;
int r3 = *static_cast<const int*>(address);
int r4 = *static_cast<int*>(address);
extern int32_t abs(int32_t);
printf("Absolute value of -3 according to C: %d", abs(-3));
extern "C" {
void call_from_c() {
printf("Just called a C++ function from C!");
}
}
const int COUNTER = 0;
void add_to_count(uint32_t inc) {
int *p = const_cast<int*>(&COUNTER);
*p += inc;
}
type Kilometers = i32;
let x: i32 = 5;
let y: Kilometers = 5;
type Thunk = Box<dyn Fn() + Send + 'static>;
let f: Thunk = Box::new(|| println!("hi"));
type Result<T> = std::result::Result<T, std::io::Error>;
pub trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize>;
fn flush(&mut self) -> Result<()>;
fn write_all(&mut self, buf: &[u8]) -> Result<()>;
fn write_fmt(&mut self, fmt: Arguments) -> Result<()>;
}
fn add_one(x: i32) -> i32 {
x + 1
}
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
f(arg) + f(arg)
}
fn main() {
let answer = do_twice(add_one, 5);
println!("The answer is: {}", answer);
}
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x + 1)
}
- The term macro refers to a family of features in Rust: declarative macros with
macro_rules!
and three kinds of procedural macros - Three kinds of procedural macros:
- Custom
#[derive]
macros that specify code added with thederive
attribute used on structs and enums - Attribute-like macros that define custom attributes usable on any item
- Function-like macros that look like function calls but operate on the tokens specified as their argument
- Custom
- Difference between Macros and Functions
- A function signature must declare the number and type of parameters the function has.
- Macros, on the other hand, can take a variable number of parameters
- Also, macros are expanded before the compiler interprets the meaning of the code, so a macro can, for example, implement a trait on a given type. A function can’t, because it gets called at runtime and a trait needs to be implemented at compile time.
- you must define macros or bring them into scope before you call them in a file, as opposed to functions you can define anywhere and call anywhere.
-
The most widely used form of macros in Rust is declarative macros. At their core, declarative macros allow you to write something similar to a Rust
match
expression. -
Definition for
vec!
#[macro_export]
macro_rules! vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
-
procedural macros, which act more like functions (and are a type of procedure). Procedural macros accept some code as an input, operate on that code, and produce some code as an output rather than matching against patterns and replacing the code with other code as declarative macros do.
-
The three kinds of procedural macros (custom derive, attribute-like, and function-like) all work in a similar fashion.
-
When creating procedural macros, the definitions must reside in their own crate with a special crate type.
-
derive
Macro
use hello_macro::HelloMacro;
use hello_macro_derive::HelloMacro;
#[derive(HelloMacro)]
struct Pancakes;
fn main() {
Pancakes::hello_macro();
}
// ----- hello_macro.lib
pub trait HelloMacro {
fn hello_macro();
}
// ----- hello_macro_derive.lib
// Cargo.toml
[lib]
proc-macro = true
[dependencies]
syn = "0.14.4"
quote = "0.6.3"
// lib.rs
extern crate proc_macro;
use crate::proc_macro::TokenStream;
use quote::quote;
use syn;
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast = syn::parse(input).unwrap();
// Build the trait implementation
impl_hello_macro(&ast)
}
fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
impl HelloMacro for #name {
fn hello_macro() {
println!("Hello, Macro! My name is {}", stringify!(#name));
}
}
};
gen.into()
}
- Attribute-like macros
#[route(GET, "/")]
fn index() {
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
- Function-like macros
let sql = sql!(SELECT * FROM posts WHERE id=1);
#[proc_macro]
pub fn sql(input: TokenStream) -> TokenStream {