安装

curl https://sh.rustup.rs -sSf | sh

成功后, 将下面代码加到 ~/.bash_profile
export PATH="$HOME/.cargo/bin:$PATH"

更新:

rustup update

卸载

rustup self uninstall

创建项目

可执行项目

cargo new project_name --bin

构建项目

cargo build

它会打包到 target/debug

构建并运行项目

cargo run

发布项目

cargo build --release

它会打包到 target/release

更新依赖包版本

cargo update

默认导入的包

doc

crate

它是一个 Rust 代码的包.

可执行的项目, 称为 二进制 crate, 库项目(即可以被其他程序调用的代码) 称为 库 crate

变量

在 rust 中, 变量默认的情况下是 不可变的 immutable

常量和不可变的变量

在 rust 中, 常量是不能用 mut 来修饰的, 它不光默认不能变, 它总是不能变的.

常量声明使用 const 而不是 let, 并且必须注明值的类型.

常量只能用于常量表达式, 而不能作为函数调用的结果, 或任何其他只在运行时计算的值.

const MAX_POINTS: u32 = 100_000;

隐藏

  • 除非再次使用 let 关键字, 否则默认情况下对变量重新赋值会导致编译错误
  • 多次使用 let 时, 实际上创建了一个新的变量, 我们可以改变值的类型, 从而复用这个名字. 但一个 mut 的变量, 则不能修改它的类型, 否则会导致一个编译错误

数据类型

Rust 是一种静态类型语言, 也就是说在编译时就必须知道所有变量的类型.

  • scalar : 标量
    • 整型(有符号的, 以 i 开头, 无符号的以 u 开头, 默认为 i32, 无论是在32位, 还是64位系统上, 默认都是 i32). 十六进制(0x开头), 八进制(0o开头), 二进制(0b开头), 字节(b'A')
    • 浮点型(f32, f64, 默认为f64, 因为在现代CPU中, 它与 f32速度几乎一样快)
    • 布尔类型(bool, 它只有 truefalse)
    • 字符类型(char, 它使用单引号指定. 不同与字符串使用双引号)
  • compound : 复合
    • 元组(tuple), 它使用一个括号中的逗号分隔的值列表来创建一个元组.let tup: (i32, f64, u8) = (500, 6.4, 1);, 这元素的类型是不必相同的.可以使用模式匹配来解构元组. let (x, y, z) = tup;
    • 数组(array), 元素类型必须相同. 而且数组长度是固定的.索引从0开始.

函数

Rust 使用 snake case 风格来命名函数的. 即所有字母都是小写, 并使用下划线分隔单词.

Rust 可以调用定义过了的函数来调用(不管是在调用者之前还是之后定义, 都可以)

参数

fn main() {
    another_function(5);
}

fn another_function(x: i32) {
    println!("The value of x is: {}", x);
}

语句和表达式

语句: 执行一些操作但不返回值(如 let 语句) 表达式: 计算并产生一个值.它可以是语句的一部分. 例如函数调用, 宏调用.注意 {} 也是一个表达式.所以可以这样子:

let y = {
        let x = 3;
        x + 1
    };

这里的{}表达式的值为4.所以 y 为 4

返回值

函数的返回值, 等同于函数体最后一个表达式的值.

fn five() -> i32 {
    5
}

注意, 如果是有分号的话, 则它是语句, 而不是表达式了.

if 表达式

fn main() {
    let number = 6;

    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }
}

因为它是一个表达式, 所以它是有返回值的, 所以可以这样子:

fn main() {
    let condition = true;
    let number = if condition {
        5
    } else {
        6
    };

    println!("The value of number is: {}", number);
}

循环

loop

fn main() {
    loop {
        println!("again!");
    }
}

while

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{}!", number);

        number = number - 1;
    }

    println!("LIFTOFF!!!");
}

for

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!!!");
}

rev() 用来反转.

所有权 ownership

一般程序管理内存的方式

  1. 使用GC
  2. 手工处理

而 Rust 则使用第三种: 内存被一个所有权系统管理, 它拥有一系列的规则使编译器在编译时进行检查.并且不会导致运行时开销.

所有权规则

  • Rust 中每一个值都有一个称之为其 所有者 owner 的变量
  • 值有且只能有一个所有者
  • 当所有者(变量)离开作用域, 这个值将被丢弃.(当变量离开作用域时, Rust 为其调用一个特殊的函数, 这个函数叫 drop)

之前提到的 数据类型 部分都是储存在 上的并且离开作用域时被移出栈.

注意, Rust 永远也不会自动创建数据的 深拷贝, 因此任何自动的复制可以被认为对运行时性能影响较小.

数据的交互方式

移动

let s1 = String::from("hello");
let s2 = s1;

克隆

String 有个 clone 函数, 它会深度复制堆上的数据.

let s1 = String::from("hello");
let s2 = s1.clone();
只在栈上的数据: 拷贝
let x = 5;
let y = x;

Rust 有一个 Copy trait 的特殊注解, 可以用在类似整型这样的储存在栈上的类型. 如果一个类型拥有 Copy trait, 一个旧的变量在将其赋值给其他变量后仍然可用.

Rust 不允许自身或任何部分实现了 Drop trait 的类型使用 Copy trait . 以下是一些 Copy trait 的建议规则

  • 所有整数类型
  • 布尔类型
  • 浮点类型
  • 元组, 当且仅当其包含的类型也是 Copy 的时候.

引用与借用

&s1 语法允许我们创建一个 指向 值 s1 的引用,但是并不拥有它。因为并不拥有这个值,当引用离开作用域时其指向的值也不会被丢弃。

获取引用作为函数参数称为借用. 默认情况下, 引用也同样是不可变的.

可变引用

限制: 在特定作用域中的特定数据, 有且只有一个可变引用. 也不能在拥有不可变引用的同时拥有可变引用.

&mut

fn main() {
    let mut s = String::from("hello");

    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

这使用 Rust 在编译时就避免了数据竞争.

悬垂指针

是其指向的内存可能已经被分配给其它持有者

在 Rust 中, 编译器确保引用永远也不会变成悬垂指针.

比如这代码在 Rust 中是编译不了的:

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s
}

解决的办法是返回 String 而不是 &String . 返回 String 就表示所有权被转移出去了, 所以值还没有释放.

引用规则

  • 在任意给定时间, 只能拥有以下中的一个
    • 一个可变引用
    • 任意数量的不可变引用
  • 引用必须总是有效的

slices

这也是一个没有所有权的类型.

slice 允许你引用集合中一段连续的元素序列,而不用引用整个集合。

字符串 slice

let s = String::from("hello world");

let hello = &s[0..5];
let world = &s[6..11];

字符串字面值就是 slice, 即 let s = "Hello, world!"; 的类型是 &str, 这是一个不可变的引用.

结构体

定义

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

使用

let user1 = User {
    email: String::from("someone@example.com"),
    username: String::from("someusername123"),
    active: true,
    sign_in_count: 1,
};

如果想改变 user1 的字段值, 则要声明为 let mut user1 = xxx. Rust 并不允许只将特定字段标记为可变.

变量与字段同名时的初始化简写法

fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}

使用结构体更新语法从其他对象创建

let user2 = User {
    email: String::from("another@example.com"),
    username: String::from("anotherusername567"),
    ..user1
};

没有字段名的结构体

struct Color(i32, i32, i32);

let black = Color(0, 0, 0);

方法

它的第一个参数总是 self

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

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

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };

    println!(
        "The area of the rectangle is {} square pixels.",
        rect1.area()
    );
}

关联函数

impl 块中定义不以 self 作为参数的函数. 它们是函数, 而不是方法. 例如

impl Rectangle {
    fn square(size: u32) -> Rectangle {
        Rectangle { width: size, height: size }
    }
}

多个 impl 块

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

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

枚举

定义

enum IpAddrKind {
    V4,
    V6,
}

注意, 枚举也可以是这样子的:

enum IpAddr {
    V4(u8, u8, u8, u8),
    V6(String),
}

let home = IpAddr::V4(127, 0, 0, 1);

let loopback = IpAddr::V6(String::from("::1"));

这与Java那种枚举不太一样..

使用

let four = IpAddrKind::V4;
let six = IpAddrKind::V6;

空值与 Option 枚举

标准库的定义

enum Option<T> {
    Some(T),
    None,
}

match 控制流运算符

遇到第一个符合时就会执行相应的代码.

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u32 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

匹配 Option

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"),
    _ => (),
}

它会匹配所有的值.

if let

用来处理只匹配一个模式的值, 而忽略其他模式的情况.

let some_u8_value = Some(0u8);
match some_u8_value {
    Some(3) => println!("three"),
    _ => (),
}

如果我们只想处理 Some(3) 时, 可以这样子简写

# let some_u8_value = Some(0u8);
if let Some(3) = some_u8_value {
    println!("three");
}

模块

创建库模块

默认情况下 , cargo 就是创建库项目的, --bin 参数才是可执行项目.

cargo new lib_name

比较好的建议是:

src/lib.rs 文件中声明模块:

mod client;

mod network;

然后在其他的文件中, 提供模块的内容:

src/network.rs 的内容:

fn connect() {
}

mod server {
    fn connect() {
    }
}

注意, 这里不需要在 src/network.rs 文件中声明 mod network 了!

模块与文件系统的规则

  • 如果一个叫 foo 的模块没有子模块, 应该将 foo 的声明放入叫做 foo.rs 的文件中
  • 如果一个叫 foo 的模块有子模块, 应该将 foo 的声明放入叫做 foo/mod.rs 的文件中

私有性规则

  • 如果一个项是公有的, 它能被任何父模块访问
  • 如果一个项是私有的, 它能被其直接父模块及其子模块访问

使用 super 访问父模块

super::client::connect();

cargo 使用本地 crate

假设项目名为 rust-demo,

项目目录结构

.
├── Cargo.lock
├── Cargo.toml
├── rust-demo.iml
├── src
│   ├── lib
│   │   ├── hello.rs
│   │   └── mod.rs
│   ├── main
│   └── main.rs

mod.rs 文件内容:

pub mod hello;

hello.rs 文件内容:

pub fn say_hello() {
    println!("hello world")
}

main.rs 文件内容:

extern crate hello;


fn main() {
    hello::say_hello()
}

Cargo.toml 文件内容

[package]
name = "rust-demo"
version = "0.1.0"
authors = ["emacsist <emacsist@qq.com>"]

[lib]
name = "hello"
path = "src/lib/hello.rs"

[[bin]]
name = "main"
path = "src/main.rs"

[dependencies]

rand = "0.3.14"

集合

vector

let v: Vec<i32> = Vec::new();

或

let v = vec![1, 2, 3];

更新

v.push(5);

获取

let third: &i32 = &v[2];
let third: Option<&i32> = v.get(2);

遍历

只读:
for i in &v {
    println!("{}", i);
}


修改:
let mut v = vec![100, 32, 57];
for i in &mut v {
    *i += 50;
}

字符串

创建

let mut s = String::new();

let s = String::from("initial contents");

更新

let mut s = String::from("foo");
s.push_str("bar");

拼接

let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2;

或
let s = format!("{}-{}-{}", s1, s2, s3);

不支持索引字符串, 如 s[0]

字符串 slice

let hello = "Здравствуйте";
let s = &hello[0..4];

遍历

for c in "नमस्ते".chars() {
    println!("{}", c);
}

hash map

创建

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 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);

更新

插入一个相同的 key 名时, 会覆盖前一个值

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);

只在键没有数据时插入

use std::collections::HashMap;

let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);

scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);

根据旧值更新一个值

use std::collections::HashMap;

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;
}

错误处理

可恢复错误: Result<T, E>

不可恢复错误: panic!

默认下, panic! 宏会 unwinding 程序, 这意味着 Rust 会回溯栈并清理它遇到的每一个函数的数据. 另一种选择是终止 abort(这会使二进制文件更小). 可以在 Cargo.toml 文件中配置:

[profile.release]
panic = 'abort'
fn main() {
    panic!("crash and burn");
}

简写 unwrap 和 expect

unwrap , 如果 Result 是 Ok, 则返回 Ok 中的值, 如果是 Err, 则会调用 panic!

use std::fs::File;

fn main() {
    let f = File::open("hello.txt").unwrap();
}

expect , 类似 unwrap , 但提供一个好的错误信息.

传播错误

返回 Result<T, E>

fn read_username_from_file() -> Result<String, io::Error> {
    let f = File::open("hello.txt");

    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();

    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

简写 ? 问号运算符

向调用者返回错误的函数

并且要注意, 它只能被用于返回 Result 的函数

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)
}

泛型

泛型的函数签名:

fn largest<T>(list: &[T]) -> T {

结构体中的泛型:

struct Point<T, U> {
    x: T,
    y: U,
}

trait

定义共享的行为

定义

pub trait Summarizable {
    fn summary(&self) -> String;
}

一个 trait 可以有多个方法签名定义.

实现

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summarizable for NewsArticle {
    fn summary(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

默认实现

pub trait Summarizable {
    fn summary(&self) -> String {
        String::from("(Read more...)")
    }
}

impl Summarizable for NewsArticle {}

trait bounds

pub fn notify<T: Summarizable>(item: T) {
    println!("Breaking news! {}", item.summary());
}

生命周期

单个的生命周期注解本身没有多少意义:生命周期注解告诉 Rust 多个引用的泛型生命周期参数如何相互联系

注解法 : 'a , 这表示 有一个名为 'a 的生命周期参数.

当从函数返回一个引用,返回值的生命周期参数需要与一个参数的生命周期参数相匹配。如果返回的引用 没有 指向任何一个参数,那么唯一的可能就是它指向一个函数内部创建的值,它将会是一个悬垂引用,因为它将会在函数结束时离开作用域

结构体的生命周期:

struct ImportantExcerpt<'a> {
    part: &'a str,
}

何时不需要生命周期注解的规则

  • 每一个是引用的参数都有它自己的生命周期参数。换句话说就是,有一个引用参数的函数有一个生命周期参数:fn foo<'a>(x: &'a i32),有两个引用参数的函数有两个不同的生命周期参数,fn foo<‘a, ‘b>(x: &‘a i32, y: &‘b i32),依此类推
  • 如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数:fn foo<'a>(x: &'a i32) -> &'a i32
  • 如果方法有多个输入生命周期参数,不过其中之一因为方法的缘故为 &self&mut self,那么 self 的生命周期被赋给所有输出生命周期参数。这使得方法编写起来更简洁。

测试

fn 行之前加上 #[test] 就会将当前的函数变成测试函数.

执行 test

cargo test

assert! 宏检测 bool 结果

assert!(larger.can_hold(&smaller));

相等或不相等测试

assert_eq!(4, add_two(2));

assert_ne!(4, add_two(2));

并行或串行测试

默认会使用线程来并行的运行测试. 也可以指定线程数:

cargo test -- --test-threads=1

测试通过时也打印输出

cargo test -- --nocapture

单元测试

通过测试函数名来进行测试

cargo test one_hundred

注意, 这种情况下, 默认会执行所有以 one_hundred 名字开头的函数的.

忽略测试

#[test]
#[ignore]
fn expensive_test() {
    // code that takes an hour to run
}

只运行那些忽略的测试函数

cargo test -- --ignored

组织结构

单元测试

传统做法是在每个文件中创建包含测试函数的 tests 模块,并使用 cfg(test) 标注模块。

#[cfg(test)]

告诉 Rust 只在执行 cargo test 时才编译和运行测试代码,而在运行 cargo build 时不这么做.

集成测试

src 目录下创建一个 tests 目录.

tests/integration_test.rs:

extern crate adder;

#[test]
fn it_adds_two() {
    assert_eq!(4, adder::add_two(2));
}

命令行程序

获取参数

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    println!("{:?}", args);

    let query = &args[1];
    let filename = &args[2];
}

注意. 第0个参数是程序路径和程序名.

读取和输出文件内容

    let mut f = File::open(filename).expect("file not found");

    let mut contents = String::new();
    f.read_to_string(&mut contents)
        .expect("something went wrong reading the file");

    println!("With text:\n{}", contents);

闭包: 可以捕获环境的匿名函数

可以在一个地方创建闭包,然后在不同的上下文中执行闭包运算。不同于函数,闭包允许捕获调用者作用域中的值.

闭包类型推断和注解

闭包不要求像 fn 函数那样在参数和返回值上注明类型.

let expensive_closure = |num| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        num
    };

当然, 你也可以显式注明:

let expensive_closure = |num: u32| -> u32 {
    println!("calculating slowly...");
    thread::sleep(Duration::from_secs(2));
    num
};

多次调用闭包注意

可以创建一个存放闭包和调用闭包结果的结构体。该结构体只会在需要结果时执行闭包,并会缓存结果值,这样余下的代码就不必再负责保存结果并可以复用该值。你可能见过这种模式被称 memoization 或 lazy evaluation。

可以使用带有 Fn 系列 trait 由标准库提供的闭包.这些都实现了 trait Fn, FnMut, FnOnce 中的一个.

迭代器

它是惰性的.

let v1 = vec![1, 2, 3];

let v1_iter = v1.iter();

消费迭代器

	let v1 = vec![1, 2, 3];

    let v1_iter = v1.iter();

    let total: i32 = v1_iter.sum();

产生其他迭代器

let v1: Vec<i32> = vec![1, 2, 3];

let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();

Cargo

发布配置

默认情况下下, 使用的是 dev 配置.即 (cargo build) 时, 使用的是 dev 配置.

也可以使用 release :

cargo build --release

Cargo.toml 中默认的配置:

[profile.dev]
opt-level = 0

[profile.release]
opt-level = 3

工作空间

$ mkdir add
$ cd add
$ vim Cargo.toml

[workspace]

members = [
    "adder",
]

然后在 add 目录下执行命令创建一个二进制 crate. 然后在 add 中执行命令: cargo build

cargo new --bin adder

├── Cargo.lock
├── Cargo.toml
├── adder
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── target

可以这样子创建多个 crate.

然后编译指定的 project:

cargo build -p adder

安装依赖

cargo install ripgrep

智能指针

在 Rust 中,普通引用和智能指针的一个额外的区别是引用是一类只借用数据的指针;相反大部分情况,智能指针 拥有 他们指向的数据。 智能指针通常使用结构体实现.

Box : 在堆上存储数据,并且可确定大小

Deref trait : 将智能指针当作常规引用处理

Drop trait: 运行清理代码

Rc: 引用计数智能指针

并发

线程

创建线程

thread::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));
    }
}

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();
}

move 闭包

强制闭包获取其使用的环境值的所有权

use std::thread;

fn main() {
    let v = vec![1, 2, 3];

    let handle = thread::spawn(move || {
        println!("Here's a vector: {:?}", v);
    });

    handle.join().unwrap();
}

消息传递

创建通道

use std::sync::mpsc;

fn main() {
    let (tx, rx) = mpsc::channel();
#     tx.send(()).unwrap();
}

mpsc 是 多个生产者,单个消费者

生产者和消费者:

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();
    });
}
use std::thread;
use std::sync::mpsc;
use std::time::Duration;

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);
    }
}

共享状态

互斥器: Mutex<T>