This is my study note of rust.

Rust是一种系统级编程语言

  • 零成本抽象
    1
    rust几乎没有运行时的抽象,它的抽象过程在编译时就已经执行完成了。
  • 所有权系统
    1
    2
    3
    4
    5
    所有权系统使得编译器在编译时进行内存管理,而无需运行的垃圾回收(GC)。
    什么叫做垃圾回收机制呢(garbage collection)?就是使用动态内存分配时,程序员往往会不小心忘记
    释放内存或者多次释放相同的内存,这时候就会导致程序运行错误,垃圾回收机制就是自动检测不再使用的
    内存对象将他们释放,主要是检测对象间的引用关系,当一个对象不再被引用时,垃圾回收器会在适当的时候
    回收他们的内存。
  • 零成本的异常处理
    1
    rust使用Result和Option类型来处理错误和可选值,这些类型在编译过程中进行检查,不需要额外的运行开销。
  • 内敛汇编
    1
    开发者可以直接在rust中嵌入汇编代码,以获得最佳的性能和对底层硬件的直接访问。
  • 零开销的并发
    1
    提供内置的并发原语,避免常见的并发问题,例如数据竞争和死锁。

什么是cargo

是Rust编程语言的官方构建系统和包管理器。主要提供以下功能:

  • 项目初始化
    1
    使用cargo new可以创建一个新的rust项目,包括基础结构和配置文件。
  • 依赖管理
    1
    在Cargo.toml中指定所需的依赖项以及版本要求,随后Cargo就会自动下载、编译和构建这些依赖项。
  • 构建和测试
    1
    2
    1.Cargo build 使用Cargo build可以构建Rust项目
    2.Cargo test 使用Cargo test可以运行项目中的测试,分为单元测试和集成测试
  • 运行和调试
    1
    2
    1.Cargo run 用于运行Rust项目
    2.Cargo check 用于快速检查代码中的错误和警告
  • 发布和分发
    1
    Cargo publish用于向Rust项目发布到crates.io,这是Rust社区的官方包仓库。其他开发者就能用Cargo install命令来安装和使用你的包。

基础语法

编译

1
2
3
rustc main.rs
cargo build
// 第一次cargo build会生成cargo.lock文件,主要负责追踪项目依赖的版本

导入包

1
2
3
在Cargo.toml中加入[dependenies] 随后加所需的包,如下:
[dependencies]
rand = "0.3.14"

更新依赖

1
2
Cargo update
// 此命令会无视Cargo.lock中锁住的依赖,更新依赖库的同时更新Cargo.lock

语法

  • use

    1
    use std::io           // 预导入,例子表示导入std::io下的所有内容
  • println!

    1
    2
    3
    4
    5
    println!("hello world");
    println!("{}",data); // {}用于打印data

    #[derive(Debug)]
    println!("{:#?}",结构体变量名); // 打印结构体变量各个参数
  • 变量申明

    1
    2
    3
    4
    let 变量名:类型;           // 申明不可变变量
    let mut 变量名:类型; // 声明变量可变
    const 变量名:类型=数字; // 申明常量在其作用域内一直有效
    // 可以使用相同的名字声明新的变量,新的变量会隐藏之前声明的同名变量。
  • io::stdin

    1
    io::stdin().readline(&mut 变量)        // 从命令行读取参数并逐行输入到变量中,&表示引用
  • expect

    1
    io::stdin().readline(&mut 变量).expect("");    // 若是函数返回err则打印,否则不打印
  • String

    1
    2
    3
    4
    5
    6
    7
    8
    9
    String::new();       // 创建String对象
    String::from(“”); // 从""中创建String类型
    let mut s2 = s1; // 此时s1会被废弃,s1失效,这样就不会导致二次释放
    let mut s2 = s1.clone(); // 此时s1和s2均有效
    编译时可以确定大小的stack数据可以使用复制操作,如下:
    let num = 10;
    let num1 = num; // num1和num都有效
    注意,由于String类型实现的是move而Stack类型实现的是copy,所以当String做为传入参数时,
    除非使用引用,否则原变量会被废弃,stack类型变量当作函数传入参数时,原变量不会被废弃。
  • match

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    match 变量
    {
    case1 => 操作1,
    case => 操作2,
    }
    // 类似于case,用变量去匹配case执行相应操作
    if let
    if let Some(3) = v
    {
    // 匹配一种情况,match的语法糖
    }
  • 循环

    1
    loop{操作}                     // 可用break跳出循环,continue执行下次循环
  • 整数字面值

    1
    2
    3
    4
    5
    Decimal 98_222
    Hex 0xff
    Octal 0o44
    Binary 0b1111_0000
    Byte b'A'
  • 复合类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    1.tuple
    let data:(i32,u32, u8) = (10,0,0);
    // 访问变量使用data.0、data.1、data.2
    let (x,y,z) = data;
    // 此时x为data.0, y为data.1,z为data.2
    2.数组
    let data[i32, num] = [];
    // 数组以[类型, 长度]形式表示
    let data[3, 5];
    // 此时data相当于[3,3,3,3,3]
    数组是在Stack上分配的单个块的内存,如果使用索引来访问,超出范围时编译会通过(也可能报错),运行会报错。
  • 函数

    1
    2
    3
    4
    5
    // 必须指明传入参数的类型
    fn 函数名(传参:类型)->返回类型
    {
    操作
    }
  • 所有权

    1
    2
    3
    4
    5
    6
    7
    内存通过管理权系统进行管理,其中包含一组编译器在编译时检查规则,在运行时无开销。
    编译时大小未知的数据必须放在heap上,已知大小的数据放在Stack上。

    所有权规则:
    1.每个值都有一个变量,该变量是该值的所有者
    2.每个值同时只能有一个所有者
    3.所有者超出作用域时该值删除
  • 引用

    1
    2
    3
    4
    5
    6
    7
    1.只能有一个可变引用,防止数据竞争
    2.允许非同时创建多个可变引用
    3.不可以拥有一个可变引用和一个不可变引用
    4.可以多个不可变引用

    悬空引用的概念:
    一个指针引用了内存的某个地址,而这个地址已经被释放或者被其他人使用了。
  • 切片

    1
    2
    3
    4
    str类型
    let name = String::from("Lilei");
    let data = name[..] // 此时data为"Lilei"
    let data = name[..2] // 此时data为"Lil"
  • 结构体

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    // 需要对struct所有字段赋值,一旦有一个实例可变,那所有字段可变
    struct User
    {
    username:String,
    active:bool,
    }

    impl User // 定义User结构体内部函数
    {
    // 定义关联函数
    fn new(s:&String, a:bool)
    {
    Struct User
    {
    username:s,
    active:a,
    };
    }

    fn solve(&self)->bool
    {
    self.active
    }
    }

    let data = User
    {
    username:String::from("xiaoming"),
    active:false,
    };
    // 想基于data实例创建新的实例时,若active参数不变,则:
    let data2 = User
    {
    username:String::from("xiaohong");
    ..data
    }

    tuple struct:
    Struct Color(i32, i32, i32);
    let data = Color(0,0,0);
  • 枚举

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    enum IPAddrkind
    {
    v4(u8, u8, u8, u8),
    v6(String),
    }
    // 为枚举定义方法
    impl IPAddrkind
    {
    fn call(&self)
    {

    }
    }

    Option<T>枚举
    enum Option<T>
    {
    Some(T),
    None,
    }
  • Vector

    1
    2
    3
    4
    5
    1.创建Vector
    let v:Vec<i32> = Vec::new();
    let v = vec![1,2,3];
    2.插入vector
    v.push(1);
  • HashMap

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let mut scores:<i32, i32> = HashMap:new()             // 创建空HashMap
    insert() // 添加数据
    collect()创建HashMap:
    let mut data:HashMap<_,_> = teams.iter().zip(scores,iter()).collect();
    get()函数进行参数获取:
    let name = 1;
    let score = data.get(&name);
    遍历HashMap:
    for (k, v) in &data
    {

    }
    entry方法,检查指定的k是否对应一个v,经常和or_insert()一起使用
    data.entry(1).or_insert(2);

代码组织

1
2
3
4
5
6
7
8
9
10
11
12
13
-package(包):Cargo的特性,让你构建、测试、共享crate
-Crate(单元包):一个模块树,它可产生一个library或可执行文件
-Module(模块)、use:控制代码的组织、作用域、私有路径
-Path(路径):为struct、function或module等项命名的方式

Module:
-在一个crate内,将代码进行分组
-控制项目私有性。plublic、private
建立module:
mod关键字
可嵌套

struct默认私有,需要pub关键字设置为公共。

错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1.可恢复的作用
Result<T,E>
enum Result<T, E>
{
Ok(T), // 操作成功,返回T
Err(E), // 操作失败返回错误类型
}
.unwrap() 是match表达式的一个快捷方法,结果是Ok返回Ok的值,否则调用panic!宏
运算符?
是错误的一种快捷方式
如果Result结果是Ok:Ok中的值就是表达式的结果,然后继续执行程序
如果Result结果是Err,则Err是整个函数的返回值,就像使用了return,所以返回类型为Result的才能使用?
如果main函数下想要使用?,则fn main() -> Result<(), Box<dyn Error>>
2.不可恢复
panic!宏
当panic!宏执行时:
展开调用栈:
1.打印一个错误信息
2.展开、清理调用栈
3.退出程序
立即中止调用栈:
1.不进行清理,直接停止
2.内存需要OS进行清理
(展开改成中止->Cargo.toml中设置profile部分,panic='abort')
set RUST_BACKTREACE=0 && Cargo run // 查看回溯信息

泛型

1
2
3
4
5
6
7
8
9
10
11
1.编写的代码不是最终的代码,而是一种模板,里面有一些“占位符”
2.编译器在编译时将“占位符替换为具体的类型”
fn largest<T>(list:&[T])->T
{
...
}

struct name<T>
{
...
}
  • trait
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    1.类似虚函数,只申明,不实现,实现放在具体的结构中去实现,当然也可以定义,做为默认实现
    2.trait只能在本地crate里定义,无法为外部类型实现trait(孤儿规则)

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

    impl summary for struct名
    {
    fn summarize(&self) -> String
    {

    }
    }
    3.trait可以做为传参限制,即:
    pub fn notify(item: impl summary + Display)
    {
    ...
    }
    // 上述代码代表实现了summary和Display这个trait的item,也可写成如下形式:
    pub fn notify<T:summary + Display>(item: T)
    {
    ...
    }
    4.where语句
    pub fn notify<T, U>(item: T, item1:U)
    where
    T:summary + Display,
    U:Clone,
    {
    ...
    }
    4.覆盖实现
    impl<T:summary> ToString for T
    {
    ...
    }
    // 上述代码代表为所有实现了summary的结构体都实现ToString方法
  • 生命周期
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
        fn longest<'a>(x:'a &str, y:'a &str) -> &str
    {
    ...
    // 描述多个引用的生命周期之间的关系,不影响生命周期
    }
    生命周期的引用:
    规则一:每个引用类型的参数都有自己的生命周期
    规则二:如果只有一个输入生命周期参数,那么该生命周期会被赋给所有的输出生命周期
    规则三:如果有多个输入生命周期参数,其中一个是&self或者&mut self,那么self的
    生命周期会被赋给所有的输出生命周期参数
    'static生命周期:整个程序的持续时间
  • 测试
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    在函数上加#[test]将函数变为测试函数
    Cargo test
    想要在成功的测试中看到打印的内容: + --show-output
    根据函数名进行测试: + 测试的函数名(测试多个函数可以取他们名称的公共部分)
    忽略某些测试:代码中加入#[ignore]
    assert!宏检查测试结果:
    true:测试通过
    false:调用panic!,测试失败
    #[should_panic(expected = "")] // 恐慌时通过,否则报错,使用expected去匹配
    标准错误:
    eprintln!
  • 闭包
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    1.是匿名函数
    2.保存为变量、做为参数
    3.可在一个地方创建闭包,然后在另一个上下文中调用闭包来完成运算
    4.从其定义的作用域捕获值
    let x = 5;
    let expensive_closure = |num:i32|
    {
    num = x;
    };
    // 可以从定义的作用域把x的值直接进行使用,会产生内存开销
    闭包获取所在环境值的方式:
    1.取得所有权:FnOnce
    2.可变借用:FnMut
    3.不可变借用:Fn
    (实现Fn的闭包也实现了FnMut,实现FnMut的闭包也实现了FnOnce)
  • 迭代器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    迭代器负责:
    1.遍历每个项
    2.确定序列何时完成
    Rust迭代器是懒惰的:
    除非调用消费迭代器的方法,否则迭代器本身没有任何效果
    方法:
    1.iter方法:在不可变引用上创建迭代器
    2.into_iter方法:创建迭代器会获得所有权
    Iterator trait有些带默认实现的方法:
    其中有些方法会调用next方法,实现Iterator trait时必须实现next方法的原因之一
    调用next的方法叫做"消耗性适配器",会把迭代器耗尽
    .map方法,创建新的迭代器 参数
    .filter方法,过滤符合条件的迭代器,创建新的迭代器存储

Cargo操作

  • 通过release profile来自定义构建
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
        -预定义的
    -可自定义:使用不同的配置,对编译代码有更多的控制
    -每个profile的配置独立于其他的profile
    主要两个profile:
    dev profile:适用于开发,Cargo build
    release profile:适用于发布,Cargo build -release
    想要自定义 xxxx profile的配置:
    在Cargo.toml里添加[profile.xxxx]区域
    [profile.dev]
    opt-level = 0

    [profile.release]
    opt-release = 3
  • 发布库
    1
    2
    3
    4
    5
    6
    7
    8
    9
    发布到crates.io
    可以使用pub use来导出内部私有结构,不同的对外公共结构不需要重新组织内部代码结构
    发布操作:
    1.在crates.io上创建账号同时获得API token
    2.运行命令: cargo login [你的API token]
    // 通知cargo,你的API token存储在本地~/.cargo/credentials
    3.cargo publish
    // 需要添加元数据:①name、description(一两句话即可,会出现在crate搜索的结果里)、license、version、author
    4.cargo yank从Crates.io撤回版本 cargo yank --vers 1.0.1
  • 通过workspaces组织大工程
    1
    2
    3
    4
    共享同一个Cargo.lock和输出文件夹的包
    依赖关系:
    [dependenies]
    依赖包名 = {path = "依赖包路径"}
  • 安装库
    1
    2
    cargo install
    // 安装的二进制存放在更目录的bin文件夹下或者$HOME/.cargo/bin
  • 自定义命令拓展cargo
    1
    如果$PATH中的某个二进制时cargo-something,你可以像子命令一样运行:cargo something

智能指针

  • Box
    1
    2
    编译阶段强制代码遵守借用规则
    let data = Box::new(5);
  • Cons List
    1
    2
    每个成员由两个元素组成:1.当前项的值 2.下一个元素
    let list = Cons(1, Cons(1, Cons(3,Nil)));
  • Deref Trait 自定义解引用运算符*
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    自定义的智能指针需要解引用的话需要实现Deref的defef
    impl<T> Deref for MyBox<T>
    {
    type Target = T;

    fn defef(&self) -> &T
    {
    &self.0
    }
    }
  • Drop Trait
    1
    drop(c);
  • Rc
    1
    2
    3
    4
    Rc::clone(&a);        // 增加引用计数
    Rc::strong_count(&a); // 获得引用计数
    防止循环引用:
    Weak<T>;
  • RefCell
    1
    2
    3
    4
    -运行时检查借用规则
    borrow方法:返回智能指针Re<T>
    -borrow_mut方法
    返回智能指针RefMut<T>

无畏并发

  • 多线程可导致的问题
    1
    2
    竞争状态,以不一致的顺序访问数据或资源
    死锁:两个线程彼此等待对方使用完所持有的资源
  • thread::spawn函数可以创建新线程
    1
    2
    参数:一个闭包
    move||{} // move声明后,允许使用其他线程的数据
  • Channel
    1
    2
    3
    4
    使用mpsc::channel函数来创建Channel
    let(tx, rx) = mpsc::channel();
    tx.send(参数).unwrap();
    rx.recv().unwrap();