Rust 学习

Rust 学习

Ownership

  • 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.
//错误示范

fn takes_own(s:String){

}
fn main(){

    let s = String::from("he");
    takes_own(s);
    println!("{}", s);
}
//s的生命周期已经在takes_own函数里结束了

//正确

fn takes_own(s:&String){

}
fn main(){

    let s = String::from("he");
    takes_own(&s);
    println!("{}", s);
}

Move & Copy

Default is move.

Copy for a type should implement the Copy trait.

types that implement Copy:

  • 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 also implement Copy. For example, (i32, i32) implements Copy, but (i32, String) does not.

引用和借用

一个地址只能被引用一次,所以已经被 borrow(创建一个引用就叫 borrow)之后,再修改或者引用之后就会报错。

fn main() {

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

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

Mutable/Immutable Reference

  • At any given time, you can have either one mutable reference or any number of immutable references.
  • 我们 不能在拥有不可变引用的同时拥有可变引用。不可变引用的用户可不希望在他们的眼皮底下值就被意外的改变了!然而,多个不可变引用是可以的,因为没有哪个只能读取数据的人有能力影响其他人读取到的数据。
  • 要么只有一个用户拥有写入权限,要么多个用户

Immutable reference is safe.

Be careful of mutable reference

// Will not compile
fn main() {

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

    let r1 = &mut s;
    let r2 = &mut s;
    
    println!("{}, {}", r1, r2);
}

Only one mutable reference prevents data race.

// Will compile
let mut s = String::from("hello");


{
    let r1 = &mut s;
} // r1 goes out of scope here, so we can make a new reference with no problems.

let r2 = &mut s;

Dangling References

Rust compiler will detect these dangling pointers.

函数应用

  1. windows()函数
let slice = ['r', 'u', 's', 't'];

let iter1 = slice.windows(2);
println!("{:?}",mut iter1.any(|v| v == ['s']));
  1. 排序 PartialOrd trait 特性

特质 Trait

substrate 中大量使用

trait 是对未知类型 Self 定义的方法集。该类型也可以访问同一个 trait 中定义的 其他方法。

  1. 未知类型定义
  2. 未知类型可以访问 trait 中定义方法 -> trait 有点像父类,而未知类型是子类
    与 Java/Golang 中的接口不同点在于:

接口只能规范方法而不能定义方法,但特性可以定义方法作为默认方法

编译器自动派生 #[derive]

下面是可以自动派生的 trait:

  • 比较 trait: Eq, PartialEq, Ord, PartialOrd
  • Clone, 用来从 &T 创建副本 T
  • Copy,使类型具有 “复制语义”(copy semantics)而非 “移动语义”(move semantics)。
  • Hash,从 &T 计算哈希值(hash)。
  • Default, 创建数据类型的一个空实例。
  • Debug,使用 {:?} formatter 来格式化一个值。

运算符重载

https://rustwiki.org/zh-CN/core/ops/

例如可以通过 Add trait 重载加法运算符 (+)

use std::ops::{Add, Sub};

#[derive(Debug, Copy, Clone, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

impl Add for Point {
    type Output = Self;

    fn add(self, other: Self) -> Self {
        Self {x: self.x + other.x, y: self.y + other.y}
    }
}

impl Sub for Point {
    type Output = Self;

    fn sub(self, other: Self) -> Self {
        Self {x: self.x - other.x, y: self.y - other.y}
    }
}

assert_eq!(Point {x: 3, y: 3}, Point {x: 1, y: 0} + Point {x: 2, y: 3});
assert_eq!(Point {x: -1, y: -3}, Point {x: 1, y: 0} - Point {x: 2, y: 3});

Iterator trait

Iterator trait 只需定义一个能返回 next(下一个)元素的方法。

struct Fibonacci {
    curr: u32,
    next: u32,
}

// 为 `Fibonacci`(斐波那契)实现 `Iterator`。
// `Iterator` trait 只需定义一个能返回 `next`(下一个)元素的方法。
impl Iterator for Fibonacci {
    type Item = u32;
    
    // 我们在这里使用 `.curr` 和 `.next` 来定义数列(sequence)。
    // 返回类型为 `Option<T>`:
    //     * 当 `Iterator` 结束时,返回 `None`。
    //     * 其他情况,返回被 `Some` 包裹(wrap)的下一个值。
    fn next(&mut self) -> Option<u32> {
        let new_next = self.curr + self.next;

        self.curr = self.next;
        self.next = new_next;

        // 既然斐波那契数列不存在终点,那么 `Iterator` 将不可能
        // 返回 `None`,而总是返回 `Some`。
        Some(self.curr)
    }
}

Impl Trait 作为类型签名

任何实现了 Descriptive 特性的对象都可以作为这个函数的参数,这个函数没必要了解传入对象有没有其他属性或方法,只需要了解它一定有 Descriptive 特性规范的方法就可以了。当然,此函数内也无法使用其他的属性与方法。

fn output(object: impl Descriptive) {
    println!("{}", object.describe());
}
// 等价于

fn output<T: Descriptive>(object: T) {

    println!("{}", object.describe());
}
// 特性做返回值

fn person() -> impl Descriptive {

    Person {
        name: String::from("Cali"),
        age: 24
    }
}

trait A+ trait B 表示需要同时实现两个 trait

where 可以简化泛型的类型和约束

fn notify(item: impl Summary + Display)
// 等价于

fn notify<T: Summary + Display>(item: T)
// 等价于

fn notify<T>(item: T) where

    T: Summary + Display

使用 trait bound 有条件地实现方法

impl <A: TraitB + TraitC, D: TraitE + TraitF> MyTrait<A, D> for YourType {}

// 使用 `where` 从句来表达约束
struct YourType;

impl <A, D> MyTrait<A, D> for YourType where
    A: TraitB + TraitC,
    D: TraitE + TraitF {}

Clone

Clone trait 能帮助我们复制资源。通常,我们可以使用由 Clone trait 定义的 .clone() 方法。

父 trait

Rust 没有“继承”,但是您可以将一个 trait 定义为另一个 trait 的超集(即父 trait)。

trait Person {
    fn name(&self) -> String;
}

// Person 是 Student 的父 trait。
// 实现 Student 需要你也 impl 了 Person。
trait Student: Person {
    fn university(&self) -> String;
}

trait Programmer {
    fn fav_language(&self) -> String;
}

// CompSciStudent (computer science student,计算机科学的学生) 是 Programmer 和 Student 两者的子类。
// 实现 CompSciStudent 需要你同时 impl 了两个父 trait。
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 favorite language is {}. My Git username is {}",
        student.name(),
        student.university(),
        student.fav_language(),
        student.git_username()
    )
}

fn main() {}

关联类型

pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;
}

高级 trait - Rust 程序设计语言 中文版

宏(Macro)

  • 元编程(metaprogramming)
  • 宏并不产生函数调用,而是展开成源码,并和程序的其余部分一起被编译。
    宏的用处:
  1. 不写重复代码(DRY,Don’t repeat yourself.)。很多时候你需要在一些地方针对不同的类型实现类似的功能,这时常常可以使用宏来避免重复代码。
  2. 领域专用语言(DSL,domain-specific language)。宏允许你为特定的目的创造特定的语法。
  3. 可变接口(variadic interface)。有时你需要能够接受不定数目参数的接口,比如 println!,根据格式化字符串的不同,它需要接受任意多的参数。

使用宏而不是编写宏

可以采取可变数量的参数:https://rustwiki.org/zh-CN/rust-by-example/macros/variadics.html

声明式宏(Declarative macros):更类似于 match 的匹配而不是函数定义

#![allow(unused)]
fn main(
    let v: Vec<u32> = vec![1, 2, 3];
}

泛型

泛型可以用来代表各种各样可能的数据类型,泛型之于数据类型,类似于变量之于内存数据。

减少很多冗余代码

use std::ops::Add;

fn double<T>(i: T) -> T
  where T: Add<Output=T> + Clone + Copy {//where 
  i + i
}
fn main(){
  println!("{}",double(3_i16));
  println!("{}",double(3_i32));
}

生命周期

生命周期与引用有效性 - Rust 程序设计语言 中文版

Rust 所有权语义模型

fn longest(x: &str, y: &str) -> &str {

    if x.len() > y.len() {
        x
    } else {
        y
    }
}
fn main() {

    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {}", result);
}

上面会因为 返回值引用可能会返回过期的引用(悬垂引用) 而报错

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

现在函数签名表明对于某些生命周期 'a,函数会获取两个参数,他们都是与生命周期 'a 存在的一样长的字符串 slice。函数会返回一个同样也与生命周期 'a 存在的一样长的字符串 slice。它的实际含义是 longest 函数返回的引用的生命周期与传入该函数的引用的生命周期的较小者一致。这就是我们告诉 Rust 需要其保证的约束条件。记住通过在函数签名中指定生命周期参数时,我们并没有改变任何传入值或返回值的生命周期,而是指出任何不满足这个约束条件的值都将被借用检查器拒绝。注意 longest 函数并不需要知道 xy 具体会存在多久,而只需要知道有某个可以被 'a 替代的作用域将会满足这个签名。

// 编译错误
fn main() {

    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    }
    println!("The longest string is {}", result);
}

如果从人的角度读上述代码,我们可能会觉得这个代码是正确的。 string1 更长,因此 result 会包含指向 string1 的引用。因为 string1 尚未离开作用域,对于 println! 来说 string1 的引用仍然是有效的。然而,我们通过生命周期参数告诉 Rust 的是: longest 函数返回的引用的生命周期应该与传入参数的生命周期中较短那个保持一致。因此,借用检查器不允许示例 10-24 中的代码,因为它可能会存在无效的引用。

生命周期注释是描述引用生命周期的办法。

&i32        // 常规引用

&'a i32     // 含有生命周期注释的引用
&'a mut i32 // 可变型含有生命周期注释的引用

对于上面那个例子,需要修改 longer 函数的

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

这个是示例 10-22 中那个返回两个字符串 slice 中较长者的 longest 函数,不过带有一个额外的参数 annann** 的类型是泛型 T,它可以被放入任何实现了 where 从句中指定的 Display trait 的类型。这个额外的参数会在函数比较字符串 slice 的长度之前被打印出来,这也就是为什么 Display trait bound 是必须的。因为生命周期也是泛型,所以生命周期参数 ‘a 和泛型类型参数 T 都位于函数名后的同一尖括号列表中。**

错误处理 - 通过例子学 Rust 中文版

  • panic 主要用于测试,以及处理不可恢复的错误
  • Option 表示值存在或不存在
  • Result 当错误有可能发生,且应当由调用者处理时

OptionResult 都具有的方法:

  • unwrap() option 为 None 时/result 为 Err 时执行 panic
  • ?
  • option 为 None 时,终止函数执行且返回 None,否则返回 Some 值
  • result 为 Err 时,终止函数执行且返回 Err,否则返回 Ok 值

Option

在标准库(std)中有个叫做 Option<T>(option 中文意思是 “选项”)的枚举 类型,用于有 “不存在” 的可能性的情况。它表现为以下两个 “option”(选项)中 的一个:

Result

ResultOption 类型的更丰富的版本,描述的是可能的错误而不是可能的不存在

也就是说,Result<T,E> 可以有两个结果的其中一个:

  • Ok<T>:找到 T 元素
  • Err<E>:找到 E 元素,E 即表示错误的类型。

项目管理

如何构建一个项目

属性 attribute

Substrate 里面大量使用

  • 属性是应用于某些模块、crate 或项的元数据(metadata)
  • 当属性作用于整个 crate 时,它们的语法为 #![crate_attribute],当它们用于模块或项时,语法为 #[item_attribute](注意少了感叹号 !)。
  • 属性可以接受参数,有不同的语法形式:
  • #[attribute = "value"]
  • #[attribute(key = "value")]
  • #[attribute(value)]

Lint

  • #[allow(dead_code)] 编译器提供了 dead_code(死代码,无效代码)_lint_,这会对未使用的函数 产生警告。可以用一个属性来禁用这个 lint。

条件编译

可能通过两种不同的操作符实现:

  • cfg 属性:在属性位置中使用 #[cfg(...)]
  • cfg! 宏:在布尔表达式中使用 cfg!(...)
// 这个函数仅当目标系统是 Linux 的时候才会编译
#[cfg(target_os = "linux")]
fn are_you_on_linux() {
    println!("You are running linux!")
}

// 而这个函数仅当目标系统 **不是** Linux 时才会编译
#[cfg(not(target_os = "linux"))]
fn are_you_on_linux() {
    println!("You are *not* running linux!")
}

fn main() {
    are_you_on_linux();
    
    println!("Are you sure?");
    if cfg!(target_os = "linux") {
        println!("Yes. It's definitely linux!");
    } else {
        println!("Yes. It's definitely *not* linux!");
    }
}

自定义条件:

#[cfg(some_condition)]
fn conditional_function() {
    println!("condition met!")
}

fn main() {
    conditional_function();
}

Workspace 工作区

清单格式 - Cargo 手册 中文版

Features 条件编译和可选依赖

https://rustwiki.org/zh-CN/cargo/reference/features.html

// 表示当 feature = "std" 为 false时,设置 no_std 属性
#![cfg_attr(not(feature = "std"), no_std)]

条件编译

https://rustwiki.org/zh-CN/reference/conditional-compilation.html?highlight=cfg_attr#cfg_attr%E5%B1%9E%E6%80%A7

面向对象

https://llever.com/gentle-intro/object-orientation.zh.html

  • 封装:通过模块 mod 实现
  • 每一个 Rust 文件都可以看作一个模块
  • 类 class 对应 Rust 中的 structenum
  • 继承:通过 trait 实现部分
  • trait 只让类型继承了方法,没有继承变量
  • “如果它嘎嘎叫,那就是鸭子”。只要实现了 嘎嘎{quacks} 方法,就代表该类型是鸭子
#![allow(unused_variables)]
fn main() {
trait Quack {
    fn quack(&self);
}

struct Duck ();

// 鸭子会嘎嘎叫

impl Quack for Duck {
    fn quack(&self) {
        println!("quack!");
    }
}



struct RandomBird {
    is_a_parrot: bool
}


// 鸟也会嘎嘎叫
impl Quack for RandomBird {
    fn quack(&self) {
        if ! self.is_a_parrot {
            println!("quack!");
        } else {
            println!("squawk!");
        }
    }
}

let duck1 = Duck();
let duck2 = RandomBird{is_a_parrot: false};
let parrot = RandomBird{is_a_parrot: true};

// 把它们都当成鸭子,因为它们都会嘎嘎叫

let ducks: Vec<&Quack> = vec![&duck1,&duck2,&parrot];

for d in &ducks {
    d.quack();
}
// quack!
// quack!
// squawk!
}