Rust Basic Notes
Toolchain
Installation
# RustUp script.
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
# Setup environment variables.
echo '. $HOME/.cargo/env' >> ~/.zshrc
# Install GCC linker and OpenSSL.
sudo apt install build-essential libssl-dev pkg-config
# Done.
cargo -V
rustc -V
Cargo
Cargo Basic Commands
cargo new hello_world
cargo run
cargo build
cargo run --release
cargo build --release
cargo check
cargo generate-lockfile
cargo fmt --check
cargo clippy
cargo test
cargo install cargo-edit
cargo install cargo-release
cargo install cargo-tarpaulin
cargo install cargo-watch
cargo install cargo-workspaces
Cargo release configuration:
[workspace.metadata.release]
# cargo install cargo-release
# cargo release -x
sign-commit = true
sign-tag = true
release = false
push = false
publish = false
shared-version = true
pre-release-commit-message = "chore(release): {{version}}"
post-release-commit-message = "chore(release): {{version}}"
tag-message = "{{tag_name}}"
Cargo Cache
~/.cargo/
:
config.toml
: global configuration.credentials.toml
:cargo login
related file..crates.toml
/.crates2.json
: installed package information.bin/
: installed binaries.git/
: installed rust git repositories.git/db/
: installed git repositories.git/checkouts/
: branches of git repositories.
registry/
:crates.io
metadata and packages.registry/index/
: metadata git repository.registry/cache/
: dependencies cache (.crate
gzip files).registry/src/
: package source files.
Cargo Configuration
Cargo.toml
:
cargo-features
: 只能用于nightly
版本的feature
.[package]
: 定义项目(package
)的元信息.name
: 名称.version
: 版本.authors
: 开发作者.edition
: Rust edition..rust-version
: 支持的最小化 Rust 版本.description
: 描述.documentation
: 文档 URL.readme
: README 文件的路径.homepage
: 主页 URL.repository
: 源代码仓库的 URL.license
: 开源协议 License..license-file
: License 文件的路径..keywords
: 项目的关键词.categories
: 项目分类.workspace
: 工作空间 workspace 的路径.build
: 构建脚本的路径.links
: 本地链接库的名称.exclude
: 发布时排除的文件.include
: 发布时包含的文件.publish
: 用于阻止项目的发布.metadata
: 额外的配置信息,用于提供给外部工具.default-run
: [cargo run
] 所使用的默认可执行文件( binary ).autobins
: 禁止可执行文件的自动发现.autoexamples
: 禁止示例文件的自动发现.autotests
: 禁止测试文件的自动发现.autobenches
: 禁止 bench 文件的自动发现.resolver
: 设置依赖解析器( dependency resolver).
- Cargo target configuration:
[lib]
: Library target.[[bin]]
: Binary target.[[example]]
: Example target.[[test]]
: Test target.[[bench]]
: Benchmark target.
- Dependency tables:
[dependencies]
: 项目依赖包.[dev-dependencies]
: 用于 examples、tests 和 benchmarks 的依赖包.[build-dependencies]
: 用于构建脚本的依赖包.[target]
: 平台特定的依赖包.
[badges]
: 维护 状态.[features]
:features
可以用于条件编译.[patch]
: 推荐使用的依赖覆盖方式.[profile]
: 编译器设置和优化.[workspace]
: 工作空间的定义.
GitHub Action
- Use tool to speed up compilation.
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
CARGO_TERM_COLOR: always
jobs:
test:
name: Test Suite
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- uses: actions-rs/cargo@v1
with:
command: test
args: --all-features --workspace
rustfmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
components: rustfmt
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
components: clippy
- name: Clippy check
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-targets --all-features --workspace -- -D warnings
docs:
name: Docs
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Check documentation
env:
RUSTDOCFLAGS: -D warnings
uses: actions-rs/cargo@v1
with:
command: doc
args: --no-deps --document-private-items --all-features --workspace
publish-dry-run:
name: Publish dry run
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- uses: actions-rs/cargo@v1
with:
command: publish
args: --dry-run
coverage:
name: Code coverage
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Run cargo-tarpaulin
uses: actions-rs/tarpaulin@v0.1
with:
args: --all-features --workspace --ignore-tests --out Lcov
- name: Upload to Coveralls
if: ${{ github.event_name == 'push' }}
uses: coverallsapp/github-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: ./lcov.info
Memory Model
Stack Value
- Primitives
- Fixed size struts.
- Fixed size arrays.
- Pointers and references.
Heap Value
- Collections:
- Arrays.
- Lists.
- Strings.
- Dynamic sized objects:
- Box.
- Trait objects.
Ownership
Copy Trait
Copyable type (implement Copy
trait):
- Integer type.
- Bool type.
- Float type.
- Char type.
- Copyable Tuple type, e.g
(i32, i32)
. - Reference type (borrowing ownership).
Most these types store on stack (including reference type with vtable).
fn main() {
// Primitive type.
let a = 5;
let b = a;
// Reference type.
let x: &str = "hello, world";
let y = x;
// Deep clone on `non-Copy` type.
let s1 = String::from("hello");
let s2 = s1.clone();
// Correct.
println!("a = {}, b = {}", a, b);
println!("x = {}, y = {}", x, y);
println!("s1 = {}, s2 = {}", s1, s2);
}
fn main() {
let s1 = String::from("hello");
let s2 = s1;
// Error[E0382]: use of moved value: `s1`.
// Move occurs because `s1` has type `std::string::String`,
// which does not implement the `Copy` trait.
println!("{}, world!", s1);
}
Reference Type
Borrowing ownership with reference type:
- At same time, only one mutable reference or multiple immutable reference.
- Reference should be valid (rustc will report dangling reference error).
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()
// Leave function without drop `s`,
// due to `s` not owner string.
}
Mutable reference:
- Only one mutable reference for a value in a scope).
- Can't mutable borrow an already immutable borrowed value.
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
let r3 = &mut s;
// Error.
println!("{}, {} and {}", r1, r2, r3);
// End of r1 and r2 borrowing.
// Correct.
let r4 = &mut s;
println!("{}", r4);
}
String Type
&str
string slice reference type:
- Borrowing type.
- UTF-8 encode (1 ~ 4 bytes).
- String literal is
&str
type.
let s = String::from("hello world");
let len = s.len();
let hello = &s[0..5];
let world = &s[6..11];
let slice1 = &s[0..2];
let slice2 = &s[..2];
let slice3 = &s[4..len];
let slice4 = &s[4..];
let slice5 = &s[0..len];
let slice6 = &s[..];
String
type:
- Ownership type.
- UTF-8 encode (1 ~ 4 bytes).
fn main() {
let mut s = String::new();
s.push_str("hello,world");
s.push('!');
assert_eq!(s,"hello,world!");
let mut s = "hello,world".to_string();
s.push('!');
assert_eq!(s,"hello,world!");
let mut s = String::from("你好, 世界");
s.push('!');
assert_eq!(s,"你好, 世界!");
let s1 = String::from("hello,");
let s2 = String::from("world!");
let s3 = s1 + &s2;
assert_eq!(s3,"hello,world!");
for c in "中国人".chars() {
println!("{}", c);
}
}
Struct Type
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn build_user(email: String, username: String) -> User {
User {
email,
username,
active: true,
sign_in_count: 1,
}
}
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("username123"),
active: true,
sign_in_count: 1,
};
let user2 = User {
email: String::from("another@example.com"),
..user1
};
Tuple Struct
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
newtype
: Wrap type into tuple struct:
- Make code more readable.
- Implement 3rd traits for 3rd types.
- Hide internal details of types.
struct Meters(u32);
Unit-like Struct
struct AlwaysEqual;
let subject = AlwaysEqual;
impl SomeTrait for AlwaysEqual {}
Enum Type
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let m1 = Message::Quit;
let m2 = Message::Move{x: 1, y: 1};
let m3 = Message::ChangeColor(255, 255, 0);
}
enum Option<T> {
Some(T),
None,
}
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);
Array Type
let a: [i32; 5] = [1, 2, 3, 4, 5];
let b = [3; 5];
let slice: &[i32] = &a[1..3];
assert_eq!(slice, &[2, 3]);
fn main() {
let one = [1, 2, 3];
let two: [u8; 3] = [1, 2, 3];
let blank1 = [0; 3];
let blank2: [u8; 3] = [0; 3];
let arrays: [[u8; 3]; 4] = [one, two, blank1, blank2];
for a in &arrays {
print!("{:?}: ", a);
for n in a.iter() {
print!("\t{} + 10 = {}", n, n+10);
}
let mut sum = 0;
for i in 0..a.len() {
sum += a[i];
}
println!("\t({:?} = {})", a, sum);
}
}
Type Alias
type Meters = i32;
let x: u32 = 5;
let y: Meters = 5;
println!("x + y = {}", x + y);
type Result<T> = std::result::Result<T, std::io::Error>;
type Thunk = Box<dyn Fn() + Send + 'static>;
let f: Thunk = Box::new(|| println!("hi"));
fn takes_long_type(f: Thunk) {}
fn returns_long_type() -> Thunk {}
Type Conversion
From Trait
use std::convert::From;
#[derive(Debug)]
struct Number {
value: i32,
}
impl From<i32> for Number {
fn from(item: i32) -> Self {
Number { value: item }
}
}
fn main() {
let num = Number::from(30);
println!("My number is {:?}", num);
let int = 5;
let num: Number = int.into();
println!("My number is {:?}", num);
}
use std::convert::TryFrom;
use std::convert::TryInto;
#[derive(Debug, PartialEq)]
struct EvenNumber(i32);
impl TryFrom<i32> for EvenNumber {
type Error = ();
fn try_from(value: i32) -> Result<Self, Self::Error> {
if value % 2 == 0 {
Ok(EvenNumber(value))
} else {
Err(())
}
}
}
fn main() {
// TryFrom
assert_eq!(EvenNumber::try_from(8), Ok(EvenNumber(8)));
assert_eq!(EvenNumber::try_from(5), Err(()));
// TryInto
let result: Result<EvenNumber, ()> = 8i32.try_into();
assert_eq!(result, Ok(EvenNumber(8)));
let result: Result<EvenNumber, ()> = 5i32.try_into();
assert_eq!(result, Err(()));
}
Explicit Type Conversion
fn main() {
let a = 3.1 as i8;
let b = 100_i8 as i32;
let c = 'a' as u8;
println!("{}, {}, {}", a, b, c)
let x: i16 = 1500;
let x_: u8 = match x.try_into() {
Ok(x1) => x1,
Err(e) => {
println!("{:?}", e.to_string());
0
}
};
}
Implicit Type Conversion
target.method()
:
- Call by value:
T::method(target)
. - Call by reference:
T::method(&target)
orT::method(&mut target)
. - Call by deref: when
T: Deref<Target = U>
, then(&T).method() => (&U).method()
. - Length-non-determined collection to length-determined slice.
- Panic.
let array: Rc<Box<[T; 3]>> = ...;
let first_entry = array[0];
// 1. `Index` trait grammar sugar: array[0] => array.index(0).
// 2. Call by: value: `Rc<Box<[T; 3]>>` not impl `Index` trait.
// 3. Call by reference: `&Rc<Box<[T; 3]>>` not impl `Index` trait.
// 4. Call by reference: `&mut Rc<Box<[T; 3]>>` not impl `Index` trait.
// 5. Call by deref -> Call by value: `Box<[T; 3]>` not impl `Index` trait.
// 6. Call by deref -> Call by reference: `&Box<[T; 3]>` not impl `Index` trait.
// 7. Call by deref -> Call by reference: `&mut Box<[T; 3]>` not impl `Index` trait.
// 8. Call by deref -> Call by deref: `[T; 3]` not impl `Index` trait.
// 9. `[T; 3]` => `[T]` impl `Index` trait.
Dynamically Sized Type
DST:
- DST 无法单独被使用, 必须要通过
&
/Box
/Rc
来间接使用. str
,[T]
,dyn Trait
.
// Error!
let s1: str = "Hello there!";
let s2: str = "How's it going?";
// Ok.
let s3: &str = "on?"
let s4: Box<str> = "Hello there!".into();
// Error!
fn my_function(n: usize) {
let array = [123; n];
}
fn foobar_1(thing: &dyn MyThing) {} // OK.
fn foobar_2(thing: Box<dyn MyThing>) {} // OK.
fn foobar_3(thing: Rc<dyn MyThing>) {} // OK.
fn foobar_4(thing: MyThing) {} // ERROR!
Sized Trait
Implicit sized trait:
fn generic<T>(t: T) {}
// Auto-transform to by Rust compiler
fn generic<T: Sized>(t: T) {}
Dynamic sized generics:
fn generic<T: ?Sized>(t: &T) {}
Flow Control
If Statement
if
expression:
let number = if condition {
5
} else {
6
};
if let
expression:
let o = Some(3);
let v = if let Some(x) = o {
x
} else {
0
};
Loop Statement
For Loop Statement
for i in 1..=5 {}
for _ in 0..10 {}
for item in collection {}
for item in &collection {}
for item in &mut collection {}
for (i, v) in collection.iter().enumerate() {}
While Loop Statement
fn main() {
let mut n = 0;
while n <= 5 {
println!("{}!", n);
n = n + 1;
}
}
Loop Expression
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {}", result);
}
Pattern Matching
match target {
pattern1 => expression1,
pattern2 => {
statement1;
statement2;
expression2
},
_ => expression3
}
if let pattern = target {
statement;
expression
}
while let pattern = target {
statement;
}
Enum Pattern Matching
enum Action {
Say(String),
MoveTo(i32, i32),
ChangeColorRGB(u16, u16, u16),
}
fn main() {
let actions = [
Action::Say("Hello Rust".to_string()),
Action::MoveTo(1,2),
Action::ChangeColorRGB(255,255,0),
];
for action in actions {
match action {
Action::Say(s) => {
println!("{}", s);
},
Action::MoveTo(x, y) => {
println!("point from (0, 0) move to ({}, {})", x, y);
},
Action::ChangeColorRGB(r, g, _) => {
println!("change color into '(r:{}, g:{}, b:0)', 'b' has been ignored",
r, g,
);
}
}
}
}
Tuple Pattern Matching
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(first, .., last) => {
println!("Some numbers: {}, {}", first, last);
},
}
}
Struct Pattern Matching
struct Point {
x: i32,
y: i32,
z: i32,
}
fn main() {
let p = Point { x: 0, y: 7, z: 0 };
let Point { x: a, y: b, z: c } = p;
assert_eq!(0, a);
assert_eq!(7, b);
assert_eq!(0, c);
let origin = Point { x: 0, y: 0, z: 0 };
match origin {
Point { x, .. } => println!("x is {}", x),
}
}
fn main() {
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),
}
}
Match Guard
Combine pattern matching and if
expression:
let num = Some(4);
match num {
Some(x) if x < 5 => println!("less than five: {}", x),
Some(x) => println!("{}", x),
None => (),
}
Match Assignment
Combine pattern matching and @
expression:
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)
},
}
struct Point {
x: i32,
y: i32,
}
fn main() {
let p @ Point {x: px, y: py } = Point {x: 10, y: 23};
println!("x: {}, y: {}", px, py);
println!("{:?}", p);
let point = Point {x: 10, y: 5};
if let p @ Point {x: 10, y} = point {
println!("x is 10 and y is {} in {:?}", y, p);
} else {
println!("x was not 10 :(");
}
}
Method
struct Circle {
x: f64,
y: f64,
radius: f64,
}
impl Circle {
fn new(x: f64, y: f64, radius: f64) -> Circle {
Circle {
x,
y,
radius,
}
}
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {}
}
fn main() {
let m = Message::Write(String::from("hello"));
m.call();
}
Self
self
: 所有权转移.&self
: 不可变借用.&mut self
: 可变借用.
pub struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
pub fn new(width: u32, height: u32) -> Self {
Rectangle { width, height }
}
pub fn width(&self) -> u32 {
return self.width;
}
pub fn height(&self) -> u32 {
return self.height;
}
}
fn main() {
let rect = Rectangle::new(30, 50);
println!("{}", rect.width());
println!("{}", rect.height());
}
Generics
enum Option<T> {
Some(T),
None,
}
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 mixup<U>(self, other: Point<U>) {}
}
impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
fn add<T: std::ops::Add<T, Output = T>>(a:T, b:T) -> T {
a + b
}
fn largest<T: PartialOrd>(list: &[T]) -> T {}
- TurboFish:
generics_struct::<T>::method()
.struct.generics_method::<T>()
.
- Use associated types in traits.
Traits
pub struct Post {
pub username: String,
pub content: String
}
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
impl Summary for Post {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
fn main() {
let post = Post{username: "username".to_string(),content: "content".to_string()};
println!("1 new post: {}", post.summarize());
}
Orphan Rule
Rust can’t implement external traits on external types:
can’t implement the Display
trait on Vec<T>
in some_package
crate,
because Display
and Vec<T>
are both defined out of some_package
.
This restriction is part of a property of programs called coherence,
ensures that other people’s code can’t break your code and vice versa.
Trait Bound
fn notify(item: &impl Summary) {}
fn notify(item: &(impl Summary + Display)) {}
fn notify<T: Summary>(item: &T) {}
fn notify<T: Summary + Display>(item: &T) {}
fn notify<T, U>(t: &T, u: &U) -> i32
where T: Display + Clone,
U: Clone + Debug
{}
trait SomeTrait: BoundTrait {}
// 可以对任何实现了 Display 特征的类型调用 ToString 特征中方法.
impl<T: Display> ToString for T {}
Trait Derive
#[derive(Debug)]
#[derive(PartialEq)]
#[derive(Eq)]
#[derive(PartialOrd)]
#[derive(Ord)]
#[derive(Clone)]
#[derive(Copy)]
#[derive(Hash)]
#[derive(Default)]
trait Person {
fn name(&self) -> String;
}
trait Student: Person {
fn university(&self) -> String;
}
trait Programmer {
fn fav_language(&self) -> String;
}
trait CompSciStudent: Programmer + Student {
fn git_username(&self) -> String;
}
fn comp_sci_student_greeting(student: &dyn CompSciStudent) -> String {
format!(
"My name is {} and I attend {}. My language is {}. My Git username is {}",
student.name(),
student.university(),
student.fav_language(),
student.git_username()
)
}
Trait Object
- Define trait object:
Box<dyn some_trait>
.&dyn some_trait
.
- A trait can have trait object only when
it is
object safe
:- all methods can't return
Self
. - all methods can't be generics.
- all methods can't return
- Trait object has
'static
lifetime. - Trait object stand for dynamic distributing (runtime), generics stand for static distributing (compile time).
trait Draw {
fn draw(&self) -> String;
}
impl Draw for u8 {
fn draw(&self) -> String {
format!("u8: {}", *self)
}
}
impl Draw for f64 {
fn draw(&self) -> String {
format!("f64: {}", *self)
}
}
fn draw1(x: Box<dyn Draw>) {
x.draw();
}
fn draw2(x: &dyn Draw) {
x.draw();
}
fn main() {
let x = 1.1f64;
let y = 8u8;
draw1(Box::new(x));
draw1(Box::new(y));
draw2(&x);
draw2(&y);
}
Associated Types
Associated types make code become readable and concise:
trait Container<A,B> {
fn contains(&self,a: A,b: B) -> bool;
}
fn difference<A,B,C>(container: &C) -> i32
where
C : Container<A,B> {}
trait Container{
type A;
type B;
fn contains(&self, a: &Self::A, b: &Self::B) -> bool;
}
fn difference<C: Container>(container: &C) {}
For all generic trait,
use associated types better than <T>
.