当前位置:   article > 正文

Rust笔记_rust self self

rust self self

一、学习笔记

0、需要补充的内容

1、派生trait-Debug

2.3 所有权和借用

1、借用规则总结

总的来说,借用规则如下:

  • 同一时刻,你只能拥有要么一个可变引用, 要么任意多个不可变引用
  • 引用必须总是有效的

2、作用域

  • 引用的作用域从s开始,持续到最后一次使用
  • 变量作用域持续到最后一个花括号 }

2.4 复合类型

1、字符串切片String 类型部分的引用

对于字符串而言,切片就是对 String 类型中某一部分的引用,它看起来像这样:

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

let hello = &s[0..5];
let world = &s[6..11];
  • 1
  • 2
  • 3
  • 4

2、字符串切片是引用

之前提到过字符串字面量,但是没有提到它的类型:

let s = "Hello, world!";
  • 1

实际上,s 的类型是 &str,因此你也可以这样声明:

let s: &str = "Hello, world!";
  • 1

该切片指向了程序可执行文件中的某个点,这也是为什么字符串字面量是不可变的,因为 &str 是一个不可变引用。

3、字符串切片要小心

前文提到过,字符串切片是非常危险的操作,因为切片的索引是通过字节来进行,但是字符串又是 UTF-8 编码,因此你无法保证索引的字节刚好落在字符的边界上,例如:

let hello = "中国人";

let s = &hello[0..2];
  • 1
  • 2
  • 3

运行上面的程序,会直接造成崩溃:

thread 'main' panicked at 'byte index 2 is not a char boundary; it is inside '中' (bytes 0..3) of `中国人`', src/main.rs:4:14
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
  • 1
  • 2

这里提示的很清楚,我们索引的字节落在了 字符的内部,这种返回没有任何意义。

4、操作字符串的方法

追加、插入、替换、删除、链接

5、字符和字符串

下面是一个示例,演示了字符和字符串的区别:

sfn main() {
    let my_char = 'A';
    let my_string = "Hello, Rust!";

    println!("Character: {}", my_char);
    println!("String: {}", my_string);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这个例子中,my_char 是一个字符变量,存储了一个 Unicode 标量值 'A',它占用 4 个字节的内存空间。

my_string 是一个字符串变量,存储了一个 UTF-8 编码的字符串字面量 "Hello, Rust!"。这个字符串由 13 个字节组成,每个字符的字节数取决于其 Unicode 标量值对应的 UTF-8 编码长度。在这个例子中,大多数字符都只需要占用一个字节,只有字符 é 需要占用两个字节。

总之,字符类型(char)占用 4 个字节的内存空间,而字符串类型(strString)使用 UTF-8 编码,根据字符的 Unicode 标量值的不同,占用的字节数会有所变化。这种变长编码有助于节省字符串占用的内存空间。

6、结构体

1、实现了所有权转移的字段无法使用。

 let user2 = User {
        email: String::from("another@example.com"),
        ..user1
    };
  • 1
  • 2
  • 3
  • 4

因为 user2 仅仅在 email 上与 user1 不同,因此我们只需要对 email 进行赋值,剩下的通过结构体更新语法 ..user1 即可完成。

.. 语法表明凡是我们没有显式声明的字段,全部从 user1 中自动获取。需要注意的是 ..user1 必须在结构体的尾部使用。

实现了 Copy 特征的类型无需所有权转移,可以直接在赋值时进行 数据拷贝,其中 boolu64 类型就实现了 Copy 特征,因此 activesign_in_count 字段在赋值给 user2 时,仅仅发生了拷贝,而不是所有权转移。

2、

我们使用 {} 来格式化输出,那对应的类型就必须实现 Display 特征,以前学习的基本类型,都默认实现了该特征:

7、枚举值和枚举类型

枚举类型是一个类型,它会包含所有可能的枚举成员, 而枚举值是该类型中的具体某个成员的实例。

8、枚举值和枚举类型关联

不仅如此,同一个枚举类型下的不同成员还能持有不同的数据类型,例如让某些花色打印 1-13 的字样,另外的花色打印上 A-K 的字样:

enum PokerCard {
    Clubs(u8),
    Spades(u8),
    Diamonds(char),
    Hearts(char),
}

fn main() {
   let c1 = PokerCard::Spades(5);
   let c2 = PokerCard::Diamonds('A');
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

9、数组

(1)三要素:长度固定、元素相同类型、线性排列

(2)数组类型包括[T;n],切片类型和数量

2.5 流程控制

1、for循环

使用方法等价使用方式所有权
for item in collectionfor item in IntoIterator::into_iter(collection)转移所有权
for item in &collectionfor item in collection.iter()不可变借用
for item in &mut collectionfor item in collection.iter_mut()可变借用

2.6 模式匹配

1、match功能(匹配、赋值、绑定)

(1)匹配(用于处理多种情况)

match target {
    模式1 => 表达式1,
    模式2 => {
        语句1;
        语句2;
        表达式2
    },
    _ => 表达式3
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

(2)使用 match 表达式赋值

还有一点很重要,match 本身也是一个表达式,因此可以用它来赋值:

enum IpAddr {
   Ipv4,
   Ipv6
}

fn main() {
    let ip1 = IpAddr::Ipv6;
    let ip_str = match ip1 {
        IpAddr::Ipv4 => "127.0.0.1",
        _ => "::1",
    };

    println!("{}", ip_str);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

(3)模式中取出绑定的值,例如:

#[derive(Debug)]
enum UsState {
    Alabama,
    Alaska,
    // --snip--
}

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState), // 25美分硬币
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

其中 Coin::Quarter 成员还存放了一个值:美国的某个州(因为在 1999 年到 2008 年间,美国在 25 美分(Quarter)硬币的背后为 50 个州印刷了不同的标记,其它硬币都没有这样的设计)。

接下来,我们希望在模式匹配中,获取到 25 美分硬币上刻印的州的名称:

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
        },
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2、Option值(可以解构出来里面的值)

例如,在以下代码中,Some(42) 表示存在值 42,而 None 表示值的缺失:

rustCopy codelet some_value: Option<i32> = Some(42);
let no_value: Option<i32> = None;
  • 1
  • 2

因此,可以说 SomeNoneOption 类型的变体,用于表示值的存在性或缺失。

使用 Option<T>,是为了从 Some 中取出其内部的 T 值以及处理没有值的情况,为了演示这一点,下面一起来编写一个函数,它获取一个 Option<i32>,如果其中含有一个值,将其加一;如果其中没有值,则函数返回 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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

传入SomeNone都可以匹配Option

3、全模式列表(各种匹配罗列出来,方便匹配)

  1. 匹配字面值

直接匹配面值,如1、2、3

  1. 匹配命名变量

  2. 单分支模式 1|2 或

  3. 通过序列 …= 匹配值的范围

  4. 解构并分解值

  • 解构结构体
  • 解构枚举(枚举是成员+数据类型)
  • 结构嵌套的结构体和枚举(用元组嵌套)

2.7 方法Method

1、self、&self 和 &mut self

方法是针对自己的方法,不用重复self类型

  • self 表示 Rectangle 的所有权转移到该方法中,这种形式用的较少
  • &self 表示该方法对 Rectangle 的不可变借用
  • &mut self 表示可变借用

2、一些注意事项

  • Method利用self减少自身的重复定义
  • impl跨域多个实现

2.8 泛型和特征

1、泛型Generics

1、结构体中使用泛型

  • 需要提前声明

  • x和y是相同类型

2、方法中实现泛型要定义

类似函数fn largest(list: &[T]) -> T {

该泛型函数的作用是从列表中找出最大的值,其中列表中的元素类型为 T。首先 largest<T> 对泛型参数 T 进行了声明,然后才在函数参数中进行使用该泛型参数 list: &[T]

2、特征Trait

1、定义:特征是一组可以被共享的行为,只要实现了特征,就能使用这组行为。

2、孤儿规则:

关于特征实现与定义的位置,有一条非常重要的原则:如果你想要为类型 A 实现特征 T,那么 A 或者 T 至少有一个是在当前作用域中定义的! 例如我们可以为上面的 Post 类型实现标准库中的 Display 特征,这是因为 Post 类型定义在当前的作用域中。同时,我们也可以在当前包中为 String 类型实现 Summary 特征,因为 Summary 定义在当前作用域中。

但是你无法在当前作用域中,为 String 类型实现 Display 特征,因为它们俩都定义在标准库中,其定义所在的位置都不在当前作用域,跟你半毛钱关系都没有,看看就行了。

该规则被称为孤儿规则,可以确保其它人编写的代码不会破坏你的代码,也确保了你不会莫名其妙就破坏了风马牛不相及的代码。

3、默认实现

默认实现无需再实现该方法,或者可以重载,也可以在默认方法调用其他实现方法

4、使用特征作为函数参数(传递实现了这个特征的实例)

现在,先定义一个函数,使用特征作为函数参数:

pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}
  • 1
  • 2
  • 3

impl Summary,只能说想出这个类型的人真的是起名鬼才,简直太贴切了,顾名思义,它的意思是 实现了Summary特征item 参数。

你可以使用任何实现了 Summary 特征的类型作为该函数的参数,同时在函数体内,还可以调用该特征的方法,例如 summarize 方法。具体的说,可以传递 PostWeibo 的实例来作为参数,而其它类如 String 或者 i32 的类型则不能用做该函数的参数,因为它们没有实现 Summary 特征。

5、特征约束

(1)有简单的写法

现在,先定义一个函数,使用特征作为函数参数:

pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}
  • 1
  • 2
  • 3

(2)复杂的写法可以实现约束功能,比如一定要同一类型、多个限制之类的

pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}
  • 1
  • 2
  • 3

(3) 使用特征约束有条件的实现方法和特征

impl< T:特征1> 类型名{

}

为有特征1的类型T实现方法

impl< T:特征1> 特征2 for T{

}

为有特征1的类型T实现特征2

6、函数返回中的impl trait

返回的真实类型非常复杂时,可以用这种方法,只要返回有这个特征的类型即可,但是只能有一个具体类型。
解决方法后续提到

3、特征对象

1、定义:

特征对象指向实现了 某个特征(Draw)的类型的实例,也就是指向了 Button 或者 SelectBox 的实例,这种映射关系是存储在一张表中,可以在运行时通过特征对象找到具体调用的类型方法。

2、重点:

dyn 关键字只用在特征对象的类型声明上,在创建时无需使用 dyn

事先知道有哪些对象,可以用枚举+循环、不知道用特征对象

3、dyn不能作为单独特征对象的定义、因为不知道大小

4、特征对象

  • 特征对象是动态分发的
  • 特征对象大小不固定
  • 几乎总是使用特征对象的引用方式

5、Self与self

在Rust中,selfSelf是两个不同的标识符,具有不同的含义和用法。

  1. self(小写)是一个关键字,通常用作方法的第一个参数。它表示当前类型的实例或引用。当定义和实现方法时,self用于引用调用该方法的对象实例。它类似于其他面向对象语言中的thisself关键字。

    例如:

    rustCopy codestruct Rectangle {
        width: u32,
        height: u32,
    }
    
    impl Rectangle {
        fn area(&self) -> u32 {
            self.width * self.height
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在上面的示例中,self用于引用Rectangle结构体实例的成员变量。

  2. Self(大写)是一个特殊的类型标识符,表示当前类型本身。它通常用于实现具有关联类型(associated types)的trait或泛型代码中,表示使用实现这些trait或代码的具体类型。

    例如:

    rustCopy codetrait SomeTrait {
        type Item;
        fn process(item: Self::Item);
    }
    
    struct SomeStruct;
    
    impl SomeTrait for SomeStruct {
        type Item = u32;
        fn process(item: Self::Item) {
            // 处理操作
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在上面的示例中,Self用作trait SomeTrait中关联类型Item的具体实现类型。它表示使用实现SomeTrait的具体类型(在此例中是SomeStruct)。

总结:self是一个关键字,表示当前类型的实例或引用,用于方法参数的引用。Self是一个特殊的类型标识符,表示当前类型本身,通常用于实现具有关联类型的trait或泛型代码中,表示使用实现这些trait或代码的具体类型。

6、特征对象的限制

不是所有特征都能拥有特征对象,只有对象安全的特征才行。当一个特征的所有方法都有如下属性时,它的对象才是安全的:

  • 方法的返回类型不能是 Self
  • 方法没有任何泛型参数

4、进一步深入特征

1、当使用泛型类型参数时,可以为其指定一个默认的具体类型,例如标准库中的 std::ops::Add 特征:

trait Add<RHS=Self> {
    type Output;

    fn add(self, rhs: RHS) -> Self::Output;
}
  • 1
  • 2
  • 3
  • 4
  • 5

下面的例子,我们来创建两个不同类型的相加:

use std::ops::Add;

struct Millimeters(u32);
struct Meters(u32);

impl Add<Meters> for Millimeters {
    type Output = Millimeters;

    fn add(self, other: Meters) -> Millimeters {
        Millimeters(self.0 + (other.0 * 1000))
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

这里,是进行 Millimeters + Meters 两种数据类型的 + 操作,因此此时不能再使用默认的 RHS,否则就会变成 Millimeters + Millimeters 的形式。使用 Add<Meters> 可以将 RHS 指定为 Meters,那么 fn add(self, rhs: RHS) 自然而言的变成了 MillimetersMeters 的相加。

也可以改为

use std::ops::Add;

struct Millimeters(u32);
struct Meters(u32);

impl Add<Meters,Output = Millimeters> for Millimeters {
    type Output;

    fn add(self, other: Meters) -> Millimeters {
        Millimeters(self.0 + (other.0 * 1000))
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

2、调用同名方法

优先调用类型上的方法

调用特征上的方法,需要显式调用(有 self 参数)

完全限定语法是调用函数最为明确的方式:

fn main() {
    println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}
  • 1
  • 2
  • 3

在尖括号中,通过 as 关键字,我们向 Rust 编译器提供了类型注解,也就是 Animal 就是 Dog,而不是其他动物,因此最终会调用 impl Animal for Dog 中的方法,获取到其它动物对狗宝宝的称呼:puppy

言归正题,完全限定语法定义为:

<Type as Trait>::function(receiver_if_method, next_arg, ...);
  • 1

3、在外部类型上实现外部特征(newtype)

简而言之:就是为一个元组结构体创建新类型。该元组结构体封装有一个字段,该字段就是希望实现特征的具体类型。

调用:需要先用 self.0 取出数组,然后再进行调用。

Rust 提供了一个特征叫 Deref,实现该特征后,可以自动做一层类似类型转换的操作,可以将 Wrapper 变成 Vec<String> 来使用。这样就会像直接使用数组那样去使用 Wrapper,而无需为每一个操作都添加上 self.0

2.9 集合类型

1、动态数组Vector

1、创建

关联函数创建、宏创建

2、访问

用下标或者.get

3、迭代遍历Vector中的元素

for i in &v {

}

4、存储不同类型的元素

使用枚举类型和特征对象来实现不同类型元素的存储

2、KV存储HashMap

1、创建

使用new方法创建,使用迭代器和collect方法创建

迭代器中的元素收集后,转成 HashMap

fn main() {
    use std::collections::HashMap;

    let teams_list = vec![
        ("中国队".to_string(), 100),
        ("美国队".to_string(), 10),
        ("日本队".to_string(), 50),
    ];

    let teams_map: HashMap<_,_> = teams_list.into_iter().collect();
    
    println!("{:?}",teams_map)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

代码很简单,into_iter 方法将列表转为迭代器,接着通过 collect 进行收集,不过需要注意的是,collect 方法在内部实际上支持生成多种类型的目标集合,因此我们需要通过类型标注 HashMap<_,_> 来告诉编译器。

2、查询HashMap

通过Get方法,获取Option<&i32> 类型,或者值i32类型

let score: i32 = scores.get(&team_name).copied().unwrap_or(0);
  • 1

.copied() 是一个方法调用,用于对获取的值进行复制操作,将其转换为 i32 类型的值。

  • 这是因为哈希映射中存储的是值的引用,而我们需要获取具体的值。
  • copied() 方法会使用 Copy trait 来复制值,因此被复制的类型必须实现 Copy trait。

.unwrap_or(0) 是一个方法调用,用于获取复制后的值,如果没有找到对应的值,则返回给定的默认值 0

  • 如果 scores.get(&team_name) 返回 Some(value),则 .unwrap_or(0) 会返回 value
  • 如果 scores.get(&team_name) 返回 None,表示没有找到对应的值,则 .unwrap_or(0) 会返回默认值 0

还可以通过循环方式遍历KV对

3、更新HashMap的值

2.13 注释和文档

1、行注释、块注释

// 我是Sun… // face

​ /* 我 是 S u n … 哎,好长! */

2、文档行注释///、块注释/** … */

以上代码有几点需要注意:

  • 文档注释需要位于 lib 类型的包中,例如 src/lib.rs
  • 文档注释可以使用 markdown语法!例如 # Examples 的标题,以及代码块高亮
  • 被注释的对象需要使用 pub 对外可见,记住:文档注释是给用户看的,内部实现细节不应该被暴露出去

cargo doc --open 命令,可以在生成文档后,自动在浏览器中打开网页

2.11 返回值和错误处理

1、panic 深入剖析

1、panic! 遇到不可恢复的错误

2、panic! 涉及被动调用和主动调用

被动:

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

    v[99];
}
  • 1
  • 2
  • 3
  • 4
  • 5

主动:

fn main() {
    panic!("crash and burn");
}
  • 1
  • 2
  • 3

3、backtrace栈展开

RUST_BACKTRACE=1 cargo run` 或 `$env:RUST_BACKTRACE=1 ; cargo run
  • 1

上面的代码就是一次栈展开(也称栈回溯),它包含了函数调用的顺序,当然按照逆序排列:最近调用的函数排在列表的最上方。

4、线程 panic 后,程序是否会终止?

子线程panic不会导致整个程序结束、main线程会

5、何时使用panic!

可能导致全局有害状态时

有害状态大概分为几类:

  • 非预期的错误
  • 后续代码的运行会受到显著影响
  • 内存安全的问题

2、返回值Result和?

1、如何获知变量类型或者函数的返回类型

  • 第一种是查询标准库或者三方库文档,搜索 File,然后找到它的 open 方法
  • Rust IDE 章节,我们推荐了 VSCode IDE 和 rust-analyzer 插件,如果你成功安装的话,那么就可以在 VSCode 中很方便的通过代码跳转的方式查看代码,同时 rust-analyzer 插件还会对代码中的类型进行标注,非常方便好用!
  • 你还可以尝试故意标记一个错误的类型,然后让编译器告诉你

2、错误处理

match匹配(具体错误应对及分析)

失败就 panic: unwrap 和 expect (原型、示例)

  • unwrap遇到错误直接panic

  • expectunwrap 很像,也是遇到错误直接 panic, 但是会带上自定义的错误提示信息

3、?的使用

它的作用跟 match 几乎一模一样:

let mut f = match f {
    // 打开文件成功,将file句柄赋值给f
    Ok(file) => file,
    // 打开文件失败,将错误返回(向上传播)
    Err(e) => return Err(e),
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如果结果是 Ok(T),则把 T 赋值给 f,如果结果是 Err(E),则返回该错误,所以 ? 特别适合用来传播错误。

4、?链式传播

强中自有强中手,一码更比一码短:

use std::fs::File;
use std::io;
use std::io::Read;

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)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

? 还能实现链式调用,File::open 遇到错误就返回,没有错误就将 Ok 中的值取出来用于下一个方法调用

5、Result 通过 ? 返回错误,那么 Option 就通过 ? 返回 None

fn first(arr: &[i32]) -> Option<&i32> {
   let v = arr.get(0)?;
   Some(v)
}
  • 1
  • 2
  • 3
  • 4

上面的函数中,arr.get 返回一个 Option<&i32> 类型,因为 ? 的使用,如果 get 的结果是 None,则直接返回 None,如果是 Some(&i32),则把里面的值赋给 v

初学者在用 ? 时,老是会犯错,例如写出这样的代码:

fn first(arr: &[i32]) -> Option<&i32> {
   arr.get(0)?
}
  • 1
  • 2
  • 3

这段代码无法通过编译,切记:? 操作符需要一个变量来承载正确的值,这个函数只会返回 Some(&i32) 或者 None,只有错误值能直接返回,正确的值不行,所以如果数组中存在 0 号元素,那么函数第二行使用 ? 后的返回类型为 &i32 而不是 Some(&i32)。因此 ? 只能用于以下形式:

  • let v = xxx()?;
  • xxx()?.yyy()?;

6、带返回值的 main 函数

main返回值是(),可以改成 fn main() -> Result<(), Box>

2.12 包和模块

1、 包Crate

  • 项目(Package):可以用来构建、测试和分享包
  • 工作空间(WorkSpace):对于大型项目,可以进一步将多个包联合在一起,组织成工作空间
  • 包(Crate):一个由多个模块组成的树形结构,可以作为三方库进行分发,也可以生成可执行文件进行运行
  • 模块(Module):可以一个文件多个模块,也可以一个文件一个模块,模块可以被认为是真实项目中的代码组织单元

1、二进制Package 库Package

  • src/main.rs 是二进制包的根文件,该二进制包的包名跟所属 Package 相同,在这里都是 my-proje
  • 库类型的 Package 只能作为三方库被其它项目引用,而不能独立运行,只有之前的二进制 Package 才可以运行

P.S :Package 是一个项目工程,而包只是一个编译单元

├── Cargo.toml
├── Cargo.lock
├── src
│   ├── main.rs
│   ├── lib.rs
│   └── bin
│       └── main1.rs
│       └── main2.rs
├── tests
│   └── some_integration_tests.rs
├── benches
│   └── simple_bench.rs
└── examples
    └── simple_example.rs
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 唯一库包:src/lib.rs
  • 默认二进制包:src/main.rs,编译后生成的可执行文件与 Package 同名
  • 其余二进制包:src/bin/main1.rssrc/bin/main2.rs,它们会分别生成一个文件同名的二进制可执行文件
  • 集成测试文件:tests 目录下
  • 基准性能测试 benchmark 文件:benches 目录下
  • 项目示例:examples 目录下

2、模块Module

1、路径

  • 绝对路径,从包根开始,路径名以包名或者 crate 作为开头
  • 相对路径,从当前模块开始,以 selfsuper 或当前模块的标识符作为开头

让我们继续经营那个惨淡的小餐馆,这次为它实现一个小功能: 文件名:src/lib.rs

mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() {
    // 绝对路径
    crate::front_of_house::hosting::add_to_waitlist();

    // 相对路径
    front_of_house::hosting::add_to_waitlist();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2、结构体和枚举的可见性

  • 将结构体设置为 pub,但它的所有字段依然是私有的
  • 将枚举设置为 pub,它的所有字段也将对外可见

3、模块与文件分离

在上述例子中,将 front_of_house 模块分离到一个单独的文件中,可以按照以下的目录结构组织代码:

markdownCopy code- src
  - lib.rs
  - front_of_house
    - mod.rs
    - hosting.rs
  • 1
  • 2
  • 3
  • 4
  • 5

目录结构中有两个文件和一个文件夹:

  1. lib.rs:保留在 src 目录中的原始入口文件,用于导入和公开模块。
  2. front_of_house 文件夹:一个包含 front_of_house 模块的文件夹。
  3. mod.rs:在 front_of_house 文件夹中创建的 mod.rs 文件,用于指定子模块的公开内容。
  4. hosting.rs:在 front_of_house 文件夹中创建的 hosting.rs 文件,其中包含了 hosting 子模块的实现。

通过这样的目录结构和文件组织,你可以将 front_of_house 模块从原始的 src/lib.rs 文件中分离出来,更好地组织和维护代码。同时,使用 mod.rs 文件来指定子模块的公开内容,使代码更加清晰可读。

lib.rs 文件中,通过 mod front_of_house; 引入了 front_of_house 模块,然后使用 pub use crate::front_of_house::hosting;hosting 子模块导出到当前作用域中。

整体而言,这种目录结构和文件组织方式提供了更好的代码模块化和可维护性,并使代码结构更清晰易懂。

3 、使用use引入模块及受限可见性

1、引入模块还是函数

从使用简洁性来说,引入函数自然是更甚一筹,但是在某些时候,引入模块会更好:

  • 需要引入同一个模块的多个函数
  • 作用域中存在同名函数

2、避免同名引用

  • 模块::函数
  • as 别名引用

3、引入项再导入

pub use crate::front_of_house::hosting;
引入该模块,再被其他模块导入

4、使用第三方包

  1. 修改 Cargo.toml 文件,在 [dependencies] 区域添加一行:rand = "0.8.3"
  2. 此时,如果你用的是 VSCoderust-analyzer 插件,该插件会自动拉取该库,你可能需要等它完成后,再进行下一步(VSCode 左下角有提示)

P.S 搜索用lib.rs 下载用crates.io

5、使用 {} 简化引入方式

对于下面的同时引入模块和模块中的项:

use std::io;
use std::io::Write;
  • 1
  • 2

可以使用 {} 的方式进行简化:

use std::io::{self, Write};
  • 1

6、使用 * 引入模块下的所有项

对于编译器来说,本地同名类型的优先级更高,要小心命名冲突

7、受限的可见性

所以,如果我们想要让某一项可以在整个包中都可以被使用,那么有两种办法:

  • 在包根中定义一个非 pub 类型的 X(父模块的项对子模块都是可见的,因此包根中的项对模块树上的所有模块都可见)
  • 在子模块中定义一个 pub 类型的 Y,同时通过 use 将其引入到包根
mod a {
    pub mod b {
        pub fn c() {
            println!("{:?}",crate::X);
        }

        #[derive(Debug)]
        pub struct Y;
    }
}

#[derive(Debug)]
struct X; //在包根中定义了一个 struct 类型的 X,由于没有使用 pub 关键字修饰,它是一个非 pub 类型。这意味着 X 对于整个包内是可见的,但对于外部包是不可见的。
use a::b::Y;
fn d() {
    println!("{:?}",Y);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • pub(in crate::a) 可见范围都只是 a 模块中

9、限制性语法

pub(crate)pub(in crate::a) 就是限制可见性语法,前者是限制在整个包内可见,后者是通过绝对路径,限制在包内的某个模块内可见,总结一下:

  • pub 意味着可见性无任何限制
  • pub(crate) 表示在当前包可见
  • pub(self) 在当前模块可见
  • pub(super) 在父模块可见
  • pub(in <path>) 表示在某个路径代表的模块中可见,其中 path 必须是父模块或者祖先模块

2.14 格式化输出

1、print!,println!,format!

  • print! 将格式化文本输出到标准输出,不带换行符
  • println! 同上,但是在行的末尾添加换行符
  • format! 将格式化文本输出到 String 字符串

2、{} 与 {:?}

{} 类似,{:?} 也是占位符:

  • {} 适用于实现了 std::fmt::Display 特征的类型,用来以更优雅、更友好的方式格式化文本,例如展示给用户
  • {:?} 适用于实现了 std::fmt::Debug 特征的类型,用于调试场景

其实两者的选择很简单,当你在写代码需要调试时,使用 {:?},剩下的场景,选择 {}

二、总结归纳

1、Option<T.>

let absent_number: Option = None;

在 Rust 中,Option<T> 是一个枚举类型,用于表示一个可能存在或可能不存在的值。它有两个变体:Some(T)None

因此,你需要使用 Option<T> 的完整写法,并通过 <i32> 显式指定 absent_number 的类型为 Option<i32>。这样编译器就知道 absent_number 是一个可能存在或可能不存在的 i32 类型的值。

2、.read_line()、.expect()、.trim() 和 .parse() 、::

.read_line(), .expect(), .trim(), 和 .parse() 是标准库(std)中的方法,属于不同的模块:

  • .read_line()std::io 模块中的方法。它用于读取输入流中的一行文本并将其存储到提供的字符串变量中。
  • .expect() 也是 std::io 模块中的方法,用于处理错误并触发 panic。它接受一个错误消息作为参数,在发生错误时触发 panic 并打印错误消息。
  • .trim()std::string::String 类型的方法,用于去除字符串开头和结尾的空白字符。String 类型实现了 Deref<Target = str>,所以它可以调用 str 类型的方法,包括 .trim()
  • .parse()std::str::FromStr trait 中定义的方法。它用于将字符串解析为目标类型。在这个例子中,它被用于将经过修剪的字符串解析为 usize 类型。
  • :: 是 Rust 中的路径分隔符,用于从命名空间、模块或类型中访问相关项。它用于引用标准库(std)中的 io 模块,以及该模块中的 stdin 函数。

这些方法是 Rust 标准库提供的常用功能,可以通过引入 std::io 和其他相关的模块来使用它们。在给定的代码中,它们是通过 use std::io; 引入了 std::io 模块,所以可以直接调用这些方法。

3、所有权

  1. Rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者
  2. 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者
  3. 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)

4、克隆和浅拷贝

如果我们确实需要深度复制 String 中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做 clone 的方法。

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

println!("s1 = {}, s2 = {}", s1, s2);
  • 1
  • 2
  • 3
  • 4

浅拷贝只发生在栈上,因此性能很高,在日常编程中,浅拷贝无处不在。

再回到之前看过的例子:

let x = 5;
let y = x;

println!("x = {}, y = {}", x, y);
  • 1
  • 2
  • 3
  • 4

总结:

通用的规则: 任何基本类型的组合可以 Copy ,不需要分配内存或某种形式资源的类型是可以 Copy。如下是一些 Copy 的类型:

  • 所有整数类型,比如 u32
  • 布尔类型,bool,它的值是 truefalse
  • 所有浮点数类型,比如 f64
  • 字符类型,char
  • 元组,当且仅当其包含的类型也都是 Copy 的时候。比如,(i32, i32)Copy 的,但 (i32, String) 就不是
  • 不可变引用 &T ,例如转移所有权中的最后一个例子,但是注意: 可变引用 &mut T 是不可以 Copy的

5、引用作用域和变量作用域

不可变引用可以存在多个

可变引用只能存在一个

引用的作用域 s 从创建开始,一直持续到它最后一次使用的地方,这个跟变量的作用域有所不同,变量的作用域从创建持续到某一个花括号 }

6、if let 返回空元组

在 Rust 中,if let 是一个表达式,它用于模式匹配并解构枚举或其他数据类型的值。在给定的代码中,if let Some(3) = v 是一个 if let 表达式,它用于匹配 v 是否是 Some(3)

如果匹配成功,即 v 的值等于 Some(3),则执行 if 代码块中的语句。在这个例子中,打印出 “three”。

如果匹配不成功,即 v 的值不等于 Some(3),则 if 代码块不会执行。

注意,if let 表达式的返回值是 (),也被称为 unit 类型。这是因为 if let 是一种控制流结构,它主要用于条件判断和执行特定的代码块,而不是返回一个具体的值。

因此,在给定的代码中,if let Some(3) = v 表达式的返回值是 (),也就是空元组。

7、#![allow(unused)]

#![allow(unused)] 是 Rust 中的一个编译器属性(attribute),用于告知编译器允许出现未使用的代码而不发出警告。当你在 Rust 代码中定义了一个变量、函数、结构体等,但在后续的代码中没有使用它们时,编译器会发出未使用的代码的警告。

通过在代码的开头添加 #![allow(unused)],你告诉编译器不要对未使用的代码发出警告。这通常用于临时的调试或实验目的,允许你编写一些暂时未使用的代码,而不被编译器警告干扰。

然而,这个属性应该谨慎使用。未使用的代码通常是无效的或者表示了一些问题,因此编译器的警告能够帮助你发现潜在的问题并进行代码优化。因此,在实际的生产代码中,通常不建议将 #![allow(unused)] 添加到整个代码库或项目中,而是应该根据需要有选择地处理未使用的代码警告。

8、方法签名及自动解引用

在 Rust 中,方法签名指的是方法的声明,它包括方法的名称、参数列表和返回类型。方法签名定义了方法的输入和输出规范,用于描述如何调用方法以及方法的行为。

当你调用一个方法时,Rust 编译器会根据方法签名对传递的对象进行自动引用和解引用操作,以使对象与方法签名匹配。这个过程称为自动引用和解引用(Automatic Reference and Dereference)。

具体来说,当你使用 object.something() 调用方法时,Rust 会根据方法签名的借用规则,自动为 object 添加 &&mut*,以便使 object 与方法签名匹配:

  • 如果方法签名的参数类型为 &self&mut self,而 object 是可变的,则 Rust 会自动在调用时将 object 解引用并借用为 &mut
  • 如果方法签名的参数类型为 &self,而 object 是不可变的,则 Rust 会自动在调用时将 object 解引用并借用为 &
  • 如果方法签名的参数类型为 self,而 object 是所有权的,则 Rust 会自动在调用时对 object 进行解引用操作。

这个自动引用和解引用的过程让方法调用更加方便,同时遵循了 Rust 的借用规则和所有权模型。它使得方法可以在不同类型的对象上进行调用,而无需显式地进行引用和解引用操作。

9、宏!(vec!)

在 Rust 中,vec! 是一个宏(macro),用于创建动态数组(动态分配的向量)。

vec! 宏的作用是在编译时根据提供的元素列表创建一个新的 Vec 类型的向量对象。它可以方便地初始化和填充向量,而不需要显式地调用 Vec::new()Vec::push() 等方法。

在给定的代码中,vec![34, 50, 25, 100, 65] 创建了一个 Vec<i32> 类型的整数向量,其中包含了元素 34、50、25、100 和 65。类似地,vec!['y', 'm', 'a', 'q'] 创建了一个 Vec<char> 类型的字符向量,其中包含了字符 ‘y’、‘m’、‘a’ 和 ‘q’。

vec! 宏的 ! 符号用于标识它是一个宏而不是普通的函数调用。这样的设计使得创建和初始化向量更加方便和直观。

总之,vec! 是一个用于创建动态数组的宏,在 Rust 中用于方便地初始化和填充向量。

10、pub

pub是Rust语言中的关键字,用于指定一个项(函数、结构体、枚举、方法等)对外可见或公开。它是"public"的缩写。

在Rust中,所有的项默认都是私有的,只能在定义它们的模块内部访问。如果想要在其他模块中使用这些项,就需要使用pub关键字将其标记为公开的。标记为pub的项可以被其他模块引用和调用。

在你提供的代码中,pub关键字被用于定义一个公共的Summary特性(trait)。特性是一种定义方法集合的抽象,类似于接口或协议的概念。通过使用pub关键字,可以将该特性对外公开,允许其他模块实现该特性,并调用其中定义的方法。

具体来说,Summary特性定义了一个抽象方法summarize,该方法接收一个&self参数并返回一个字符串。任何实现了Summary特性的类型都需要提供自己的summarize方法实现。

总结:pub关键字用于指定一个项对外可见或公开,允许其他模块引用和使用该项。在你提供的代码中,pub用于公开一个特性(trait),允许其他模块实现并调用其中定义的方法。

11、Rust可以为哪些结构实现方法?

在Rust中,可以为以下结构实现方法:

  1. 结构体(Structs):可以为自定义的结构体类型实现方法。
rustCopy codestruct MyStruct {
    // fields
}

impl MyStruct {
    // methods
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2.枚举类型(Enums):可以为枚举类型实现方法。

rustCopy codeenum MyEnum {
    // variants
}

impl MyEnum {
    // methods
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3.Trait对象(Trait Objects):可以为特定的Trait对象实现方法。

rustCopy codetrait MyTrait {
    // methods
}

impl MyTrait for dyn MyTrait {
    // methods
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  1. 原生类型(Primitive Types):可以为原生数据类型(如整数、浮点数等)实现方法,通过关联函数(Associated Functions)的形式。
rustCopy codeimpl i32 {
    // methods
}
  • 1
  • 2
  • 3

需要注意的是,Rust是一门静态类型语言,对于实现方法的类型必须在编译时就已经确定。这意味着你只能为自己定义的结构体、枚举、Trait对象等实现方法,无法为标准库或其他第三方库中的类型直接添加方法。不过可以使用特定的方式来扩展现有类型的功能,比如通过实现Trait来为现有类型添加额外的行为。Trait语法不一样,是XXforXX。

12、Rust方法和函数的区别

在Rust中,方法(Method)和函数(Function)有一些区别:

  1. 形式上的区别:
    • 方法是关联在结构体、枚举、Trait对象等类型上的函数,使用特定的语法进行定义和调用。方法使用特定的接收者(Receiver)来指定调用方法的实例。
    • 函数是独立的、不依赖于特定类型的函数,使用普通的函数定义和调用语法。
  2. 语法上的区别:
    • 方法的定义位于结构体、枚举、Trait对象等类型的impl块内部,并且方法的第一个参数是self(或&self&mut self),用于表示调用方法的实例。
    • 函数的定义位于模块中,没有与特定类型关联的语法要求,参数列表可以根据需求进行定义。
  3. 使用方式上的区别:
    • 方法是与特定类型关联的,只能通过实例调用。调用方法时,Rust会自动解引用或借用实例,以适应方法的接收者类型。
    • 函数可以在任何地方定义和调用,不依赖于特定类型的实例。函数可以接受各种参数并返回结果。

综上所述,方法是与特定类型关联的函数,使用特定的语法和语义进行定义和调用。函数是独立的、不依赖于特定类型的函数。方法用于表示特定类型的行为和操作,而函数则用于通用的计算和逻辑操作。

13、Rust中什么时候用::调用,什么时候用.调用

在Rust中,使用::.符号进行方法和函数的调用有以下区别:

  1. ::符号用于调用关联函数(Associated Functions)和静态方法(Static Methods)。这些函数和方法是与类型相关联的,而不是与实例相关联。关联函数是定义在impl块中的函数,而静态方法是使用static关键字定义的函数。
rustCopy codestruct MyStruct;

impl MyStruct {
    fn associated_function() {
        // 实现关联函数的代码
    }
}

// 调用关联函数
MyStruct::associated_function();

// 调用静态方法
MyStruct::static_method();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  1. .符号用于调用实例方法(Instance Methods)和普通函数(Regular Functions)。实例方法是定义在impl块中的方法,与特定的实例相关联。普通函数是独立的、不依赖于特定类型的函数。
rustCopy codestruct MyStruct;

impl MyStruct {
    fn instance_method(&self) {
        // 实现实例方法的代码
    }
}

let my_instance = MyStruct;
my_instance.instance_method(); // 调用实例方法

regular_function(); // 调用普通函数
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

总结起来,使用::调用关联函数和静态方法,这些函数是与类型相关联的,而使用.调用实例方法和普通函数,这些函数是与特定实例或独立于特定类型的。需要注意的是,关联函数和静态方法是通过类型名来调用的,而实例方法和普通函数是通过实例或函数名来调用的。

14、闭包

Rust闭包(Closure)是一种可以捕获其环境并作为匿名函数使用的语法构造。闭包可以在需要函数的地方使用,并具有以下特性:

  1. 匿名性:闭包是匿名函数,没有固定的函数名称。
  2. 捕获环境:闭包可以捕获其周围环境中的变量和值,并在闭包体内使用。
  3. 可移动性:闭包可以移动(Move)捕获的变量,可以在不同的上下文中使用。

闭包的语法如下:

rustCopy code
|<参数列表>| <函数体>
  • 1
  • 2
  • <参数列表>定义了闭包的参数,可以包含零个或多个参数。
  • <函数体>包含了闭包的实际代码,用于执行特定的操作。

下面是一个简单的例子:

fn main() {
    let add_numbers = |a, b| a + b;

    let result = add_numbers(5, 10);
    println!("Result: {}", result);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在这个例子中,我们定义了一个闭包add_numbers,它接受两个参数ab,并返回它们的和。闭包体中的代码a + b执行了实际的加法操作。

然后,我们通过调用闭包并传递参数510来使用闭包。闭包在这里就像一个函数调用,接收参数并返回结果。最后,我们打印了结果15

闭包是一种灵活且强大的语法构造,可以方便地在需要函数的地方使用,捕获环境变量并执行特定的操作。闭包在函数式编程和异步编程等场景中经常使用。

15、关联类型

关联类型(associated types)是 Rust 编程语言中的一个特性,用于在 trait 中指定与特定实现相关的类型。

在 trait 中定义一个关联类型可以提供更大的灵活性,因为它允许实现者选择具体的类型作为关联类型的实现,而不需要在 trait 中指定具体的类型。

关联类型通常在需要使用某种类型来表示 trait 中的某个关联概念,但具体的类型在实现 trait 时可能会有所不同的情况下使用。通过关联类型,我们可以将这种灵活性留给实现者来确定具体的类型。

使用关联类型的一个常见场景是在泛型代码中。当 trait 的某些方法依赖于具体类型时,使用关联类型可以使泛型代码更加通用,因为具体类型可以由实现者根据自己的需求来选择。

下面是一个示例,展示了如何在 trait 中定义和使用关联类型:

rustCopy codetrait Iterator {
    type Item; // 定义关联类型 Item
    
    fn next(&mut self) -> Option<Self::Item>; // 使用关联类型
}

struct Counter {
    count: u32,
}

impl Iterator for Counter {
    type Item = u32; // 实现关联类型 Item 为 u32

    fn next(&mut self) -> Option<Self::Item> {
        if self.count < 5 {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

在上述示例中,Iterator trait 定义了一个关联类型 Item,表示迭代器产生的元素的类型。next 方法返回一个 Option<Self::Item>,其中 Self::Item 表示实现该 trait 的类型所指定的关联类型。

Counter 结构体的实现中,我们将关联类型 Item 定义为 u32,表示计数器产生的元素类型为无符号 32 位整数。

通过使用关联类型,我们可以使泛型代码更加灵活,允许实现者根据具体需求选择合适的类型作为关联类型的实现。

16、as做类型转换的限制,及解决方法

在 Rust 中,as 关键字用于进行简单的类型转换,将一个值转换为另一个类型。它允许在一些基本的类型之间进行转换,例如数值类型之间的转换,如 i32u32 的转换。

例如,以下是使用 as 进行类型转换的示例:

let x: i32 = 5;
let y: u32 = x as u32;
  • 1
  • 2

在上述示例中,x 是一个 i32 类型的变量,我们使用 as 将其转换为 u32 类型,并将结果赋给 y

然而,as 关键字有一些限制:

  1. 它只能用于转换 Rust 中的基本类型,如整数、浮点数、字符等。它不能用于自定义类型之间的转换。
  2. as 是一种静态类型转换,它在编译时确定转换是否合法,并且在运行时不会进行运行时检查。这意味着,如果类型转换不是安全的或不可行的,编译器将发出警告或错误。

由于 as 的限制,当你希望在类型转换上拥有更多的控制,并且能够处理转换错误时,可以使用 TryInto trait。

TryInto trait 是 Rust 标准库提供的一个 trait,用于类型转换,并且允许对转换过程进行更多的控制。它提供了一个 try_into 方法,该方法尝试将一个值转换为目标类型,并返回一个 Result 类型,以便处理转换错误。

通过使用 TryInto trait,你可以在转换过程中检查边界条件、处理转换失败等情况,并根据实际需求进行错误处理。这使得类型转换更加灵活、安全和可控。

综上所述,当你需要更多的控制和错误处理能力时,应该使用 TryInto trait 而不是简单的 as 关键字进行类型转换。

17、.unwrap()

use std::convert::TryInto;

fn main() {
  let a: i32 = 10;
  let b: u16 = 100;

  let b_ = b.try_into()
            .unwrap();

  if a < b_ {
    println!("Ten is less than one hundred.");
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

.unwrap() 是 Rust 中用于从 Result 类型中获取值的方法。Result 是一个枚举类型,用于表示可能的操作结果,它有两个可能的值:OkErr.unwrap() 方法用于从 Result 类型中获取 Ok 的值,如果结果是 Err,则会引发 panic。

在给定的示例中,b.try_into().unwrap() 表示将 b 转换为 u16 类型,并使用 .unwrap() 获取转换后的值。如果转换成功(Ok),则返回转换后的值;如果转换失败(Err),则会发生 panic。

.unwrap() 方法在编写简单示例或确定转换结果总是成功的情况下是方便的。然而,它不适合在真实的生产代码中使用,因为它会忽略可能的错误并导致程序终止。

在实际情况下,更好的做法是使用 match 或其他错误处理机制来处理 Result 类型的结果,以便更好地处理可能的错误情况,并根据实际需求进行适当的错误处理。

18、<T: Add<T, Output = T>>为什么不是<T: Add<Output = T>>

<T: Add<T, Output = T>> 中,Add<T, Output = T> 是用于约束泛型类型参数 T 的语法。

让我们来解释这个语法的含义:

  • <T>:这部分表示泛型参数 T,用于表示待定的类型。
  • : Add<T, Output = T>:这部分表示对泛型参数 T 施加的约束条件。在这个约束中,我们要求类型 T 必须实现了 Add 特征,并且加法运算的结果类型为 T
    • Add<T>:这表示类型 T 必须实现了 Add 特征。Add 是一个 trait,用于定义加法运算的行为。
    • Output = T:这表示加法运算的结果类型必须为 T。通过这个约束,我们要求加法运算的结果类型与操作数的类型相同,都为 T

通过这样的泛型约束 <T: Add<T, Output = T>>,我们限制了类型 T 必须实现了 Add 特征,并且加法运算的结果类型为 T

关于为什么不是 <T: Add<Output = T>>,是因为我们希望约束类型 T 实现了 Add 特征,并且加法运算的结果类型为 T,也就是加法运算的结果与操作数的类型相同。如果我们写成 <T: Add<Output = T>>,则没有约束加法运算的左操作数和右操作数必须是相同类型的,而只是约束了加法运算的结果类型与操作数的类型相同。

所以,正确的泛型约束应该是 <T: Add<T, Output = T>>,用于确保类型 T 实现了 Add 特征,并且加法运算的结果类型为 T

19、Rust宏

Rust宏是Rust编程语言中的一种元编程机制,它允许在编译时进行代码生成和转换。宏是一种用于扩展Rust语法的工具,可以让开发者定义自己的代码片段,并在编译时将其插入到代码中。宏可以用于创建通用代码模板、代码重复模式的抽象以及执行其他自定义的代码转换和操作。

Rust宏有两种类型:声明式宏(Declarative Macros)和过程宏(Procedural Macros)。

声明式宏使用macro_rules!关键字定义,它们可以匹配代码的结构并根据预定义的模式进行转换。声明式宏是基于模式匹配的,允许开发者通过模式和替换部分来指定代码的转换规则。这种宏的定义和展开是基于文本替换的。

过程宏是基于函数式宏的一种扩展,允许在编译时对代码进行更复杂的操作。过程宏接收代码作为输入,并产生新的代码作为输出。它们可以分为三种类型:自定义派生(Custom Derive)宏、属性(Attribute)宏和函数式(Function-like)宏。过程宏的定义和展开是基于Rust语法树的操作。

Rust宏的主要作用是增强语言的表达能力和编写可复用代码的能力。它们可以帮助开发者简化重复的代码、提高代码的可读性和维护性,并在编译时进行静态检查,减少运行时错误。宏在Rust中广泛用于各种领域,例如创建DSL(领域特定语言)、实现代码生成、简化数据结构的定义等。

20、Self、self

在Rust中,selfSelf是两个不同的标识符,具有不同的含义和用法。

  1. self(小写)是一个关键字,通常用作方法的第一个参数。它表示当前类型的实例或引用。当定义和实现方法时,self用于引用调用该方法的对象实例。它类似于其他面向对象语言中的thisself关键字。

    例如:

    rustCopy codestruct Rectangle {
        width: u32,
        height: u32,
    }
    
    impl Rectangle {
        fn area(&self) -> u32 {
            self.width * self.height
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在上面的示例中,self用于引用Rectangle结构体实例的成员变量。

  2. Self(大写)是一个特殊的类型标识符,表示当前类型本身。它通常用于实现具有关联类型(associated types)的trait或泛型代码中,表示使用实现这些trait或代码的具体类型。

    例如:

    rustCopy codetrait SomeTrait {
        type Item;
        fn process(item: Self::Item);
    }
    
    struct SomeStruct;
    
    impl SomeTrait for SomeStruct {
        type Item = u32;
        fn process(item: Self::Item) {
            // 处理操作
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在上面的示例中,Self用作trait SomeTrait中关联类型Item的具体实现类型。它表示使用实现SomeTrait的具体类型(在此例中是SomeStruct)。

总结:self是一个关键字,表示当前类型的实例或引用,用于方法参数的引用。Self是一个特殊的类型标识符,表示当前类型本身,通常用于实现具有关联类型的trait或泛型代码中,表示使用实现这些trait或代码的具体类型。

21、as _

具体来说,as _ 用于重命名项,并将其命名为一个匿名的占位符,表示我们不会直接使用该项的名称,而是通过其他途径访问它。

以下是一个示例:

rustCopy codemod example_module {
    pub struct GraphQuery;
    
    pub trait SomeTrait {
        fn some_method(&self);
    }
    
    pub fn some_function() {
        println!("This is a function");
    }
}

use example_module::{GraphQuery as _, SomeTrait};

fn main() {
    let graph_query = example_module::GraphQuery;
    graph_query.some_method(); // 编译错误,GraphQuery 重命名为匿名
    
    
    let some_trait_impl = SomeTraitImpl;
    
    // 可以通过 SomeTraitImpl 实现的方式来访问 GraphQuery 的方法
    some_trait_impl.some_method();
    
    example_module::some_function(); // 正常调用函数
}

struct SomeTraitImpl;

impl SomeTrait for SomeTraitImpl {
    fn some_method(&self) {
        println!("This is a method");
    }
}
  • 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

在上述示例中,我们使用 use 语法导入了 example_module::GraphQuery as _SomeTrait。通过 as _,我们将 GraphQuery 重命名为匿名,表示我们不会直接使用该名称。

main 函数中,我们创建了一个 graph_query 的变量,它的类型是 example_module::GraphQuery,但我们不能直接使用 graph_query.some_method(),因为我们重命名了 GraphQuery。相反,我们使用了一个 SomeTraitImpl 的实现来调用 some_method 方法。

通过将 GraphQuery 重命名为匿名,我们表达了一个意图,即我们不会直接使用该项的名称,而是通过其他途径访问它,例如通过实现某个特定的 trait 来调用其中的方法。

使用 as _ 可以提高代码的可读性,尤其在引入大量项时,有助于减少命名冲突和提供更清晰的代码意图。

22、rust一般哪些语句会返回错误类型

在Rust中,一般以下情况会返回错误类型:

  1. 文件IO:打开文件、读写文件等操作可能会因为文件不存在、权限问题等原因返回错误。
  2. 网络通信:进行网络请求、连接服务器等操作可能因为网络问题、服务器不可用等原因返回错误。
  3. 解析数据:将字符串解析成数字、日期等数据类型时,如果格式不正确可能会返回错误。
  4. 数据转换:进行数据类型转换时,如果类型不匹配可能会返回错误。
  5. 动态内存分配:使用OptionResult的方法进行动态内存分配时,可能因为内存不足等原因返回错误。
  6. 错误处理:手动编写的错误处理逻辑中,可能根据特定条件返回自定义的错误类型。

一般情况下,Rust推荐使用Result类型来处理可能出现错误的情况。Result类型是Rust标准库中的一个枚举类型,它有两个变体:Ok(value)表示操作成功并返回值,Err(error)表示操作失败并返回错误。通过Result类型,可以清晰地传递和处理函数执行过程中可能出现的错误情况,避免使用传统的异常处理机制。

对于返回错误类型的函数,调用者通常会使用matchif let或者?操作符来处理这些错误,根据具体情况进行恰当的错误处理和错误传播。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/一键难忘520/article/detail/951234
推荐阅读
相关标签
  

闽ICP备14008679号