一、学习笔记
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];
2、字符串切片是引用
之前提到过字符串字面量,但是没有提到它的类型:
let s = "Hello, world!";
实际上,s 的类型是 &str,因此你也可以这样声明:
let s: &str = "Hello, world!";
该切片指向了程序可执行文件中的某个点,这也是为什么字符串字面量是不可变的,因为 &str 是一个不可变引用。
3、字符串切片要小心
前文提到过,字符串切片是非常危险的操作,因为切片的索引是通过字节来进行,但是字符串又是 UTF-8 编码,因此你无法保证索引的字节刚好落在字符的边界上,例如:
let hello = "中国人";
let s = &hello[0..2];
运行上面的程序,会直接造成崩溃:
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
这里提示的很清楚,我们索引的字节落在了 中 字符的内部,这种返回没有任何意义。
4、操作字符串的方法
追加、插入、替换、删除、链接
5、字符和字符串
下面是一个示例,演示了字符和字符串的区别:
sfn main() {
let my_char = 'A';
let my_string = "Hello, Rust!";
println!("Character: {}", my_char);
println!("String: {}", my_string);
}
在这个例子中,my_char 是一个字符变量,存储了一个 Unicode 标量值 'A',它占用 4 个字节的内存空间。
my_string 是一个字符串变量,存储了一个 UTF-8 编码的字符串字面量 "Hello, Rust!"。这个字符串由 13 个字节组成,每个字符的字节数取决于其 Unicode 标量值对应的 UTF-8 编码长度。在这个例子中,大多数字符都只需要占用一个字节,只有字符 é 需要占用两个字节。
总之,字符类型(char)占用 4 个字节的内存空间,而字符串类型(str 或 String)使用 UTF-8 编码,根据字符的 Unicode 标量值的不同,占用的字节数会有所变化。这种变长编码有助于节省字符串占用的内存空间。
6、结构体
1、实现了所有权转移的字段无法使用。
let user2 = User {
email: String::from("another@example.com"),
..user1
};
因为 user2 仅仅在 email 上与 user1 不同,因此我们只需要对 email 进行赋值,剩下的通过结构体更新语法 ..user1 即可完成。
.. 语法表明凡是我们没有显式声明的字段,全部从 user1 中自动获取。需要注意的是 ..user1 必须在结构体的尾部使用。
实现了 Copy 特征的类型无需所有权转移,可以直接在赋值时进行 数据拷贝,其中 bool 和 u64 类型就实现了 Copy 特征,因此 active 和 sign_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');
}
9、数组
(1)三要素:长度固定、元素相同类型、线性排列
(2)数组类型包括[T;n],切片类型和数量
2.5 流程控制
1、for循环
| 使用方法 | 等价使用方式 | 所有权 |
|---|---|---|
for item in collection | for item in IntoIterator::into_iter(collection) | 转移所有权 |
for item in &collection | for item in collection.iter() | 不可变借用 |
for item in &mut collection | for item in collection.iter_mut() | 可变借用 |
2.6 模式匹配
1、match功能(匹配、赋值、绑定)
(1)匹配(用于处理多种情况)
match target {
模式1 => 表达式1,
模式2 => {
语句1;
语句2;
表达式2
},
_ => 表达式3
}
(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);
}
(3)模式中取出绑定的值,例如:
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
// --snip--
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState), // 25美分硬币
}
其中 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
},
}
}
2、Option值(可以解构出来里面的值)
例如,在以下代码中,Some(42) 表示存在值 42,而 None 表示值的缺失:
rustCopy codelet some_value: Option<i32> = Some(42);
let no_value: Option<i32> = None;
因此,可以说 Some 和 None 是 Option 类型的变体,用于表示值的存在性或缺失。
使用 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);
传入Some 和 None都可以匹配Option
3、全模式列表(各种匹配罗列出来,方便匹配)
- 匹配字面值
直接匹配面值,如1、2、3
-
匹配命名变量
-
单分支模式 1|2 或
-
通过序列 …= 匹配值的范围
-
解构并分解值
- 解构结构体
- 解构枚举(枚举是成员+数据类型)
- 结构嵌套的结构体和枚举(用元组嵌套)
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());
}
impl Summary,只能说想出这个类型的人真的是起名鬼才,简直太贴切了,顾名思义,它的意思是 实现了Summary特征 的 item 参数。
你可以使用任何实现了 Summary 特征的类型作为该函数的参数,同时在函数体内,还可以调用该特征的方法,例如 summarize 方法。具体的说,可以传递 Post 或 Weibo 的实例来作为参数,而其它类如 String 或者 i32 的类型则不能用做该函数的参数,因为它们没有实现 Summary 特征。
5、特征约束
(1)有简单的写法
现在,先定义一个函数,使用特征作为函数参数:
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
(2)复杂的写法可以实现约束功能,比如一定要同一类型、多个限制之类的
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
(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中,self和Self是两个不同的标识符,具有不同的含义和用法。
-
self(小写)是一个关键字,通常用作方法的第一个参数。它表示当前类型的实例或引用。当定义和实现方法时,self用于引用调用该方法的对象实例。它类似于其他面向对象语言中的this或self关键字。例如:
rustCopy codestruct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } }在上面的示例中,
self用于引用Rectangle结构体实例的成员变量。 -
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) { // 处理操作 } }在上面的示例中,
Self用作traitSomeTrait中关联类型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;
}
下面的例子,我们来创建两个不同类型的相加:
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))
}
}
这里,是进行 Millimeters + Meters 两种数据类型的 + 操作,因此此时不能再使用默认的 RHS,否则就会变成 Millimeters + Millimeters 的形式。使用 Add<Meters> 可以将 RHS 指定为 Meters,那么 fn add(self, rhs: RHS) 自然而言的变成了 Millimeters 和 Meters 的相加。
也可以改为
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))
}
}
2、调用同名方法
优先调用类型上的方法
调用特征上的方法,需要显式调用(有 self 参数)
完全限定语法是调用函数最为明确的方式:
fn main() {
println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}
在尖括号中,通过 as 关键字,我们向 Rust 编译器提供了类型注解,也就是 Animal 就是 Dog,而不是其他动物,因此最终会调用 impl Animal for Dog 中的方法,获取到其它动物对狗宝宝的称呼:puppy。
言归正题,完全限定语法定义为:
<Type as Trait>::function(receiver_if_method, next_arg, ...);
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)
}
代码很简单,into_iter 方法将列表转为迭代器,接着通过 collect 进行收集,不过需要注意的是,collect 方法在内部实际上支持生成多种类型的目标集合,因此我们需要通过类型标注 HashMap<_,_> 来告诉编译器。
2、查询HashMap
通过Get方法,获取Option<&i32> 类型,或者值i32类型
let score: i32 = scores.get(&team_name).copied().unwrap_or(0);
.copied() 是一个方法调用,用于对获取的值进行复制操作,将其转换为 i32 类型的值。
- 这是因为哈希映射中存储的是值的引用,而我们需要获取具体的值。
copied()方法会使用Copytrait 来复制值,因此被复制的类型必须实现Copytrait。
.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];
}
主动:
fn main() {
panic!("crash and burn");
}
3、backtrace栈展开
RUST_BACKTRACE=1 cargo run` 或 `$env:RUST_BACKTRACE=1 ; cargo run
上面的代码就是一次栈展开(也称栈回溯),它包含了函数调用的顺序,当然按照逆序排列:最近调用的函数排在列表的最上方。
4、线程 panic 后,程序是否会终止?
子线程panic不会导致整个程序结束、main线程会
5、何时使用panic!
有害状态大概分为几类:
- 非预期的错误
- 后续代码的运行会受到显著影响
- 内存安全的问题
2、返回值Result和?
1、如何获知变量类型或者函数的返回类型
- 第一种是查询标准库或者三方库文档,搜索
File,然后找到它的open方法 - 在 Rust IDE 章节,我们推荐了
VSCodeIDE 和rust-analyzer插件,如果你成功安装的话,那么就可以在VSCode中很方便的通过代码跳转的方式查看代码,同时rust-analyzer插件还会对代码中的类型进行标注,非常方便好用! - 你还可以尝试故意标记一个错误的类型,然后让编译器告诉你
2、错误处理
match匹配(具体错误应对及分析)
失败就 panic: unwrap 和 expect (原型、示例)
-
unwrap遇到错误直接panic
-
expect跟unwrap很像,也是遇到错误直接panic, 但是会带上自定义的错误提示信息
3、?的使用
它的作用跟 match 几乎一模一样:
let mut f = match f {
// 打开文件成功,将file句柄赋值给f
Ok(file) => file,
// 打开文件失败,将错误返回(向上传播)
Err(e) => return Err(e),
};
如果结果是 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)
}
? 还能实现链式调用,File::open 遇到错误就返回,没有错误就将 Ok 中的值取出来用于下一个方法调用
5、Result 通过 ? 返回错误,那么 Option 就通过 ? 返回 None:
fn first(arr: &[i32]) -> Option<&i32> {
let v = arr.get(0)?;
Some(v)
}
上面的函数中,arr.get 返回一个 Option<&i32> 类型,因为 ? 的使用,如果 get 的结果是 None,则直接返回 None,如果是 Some(&i32),则把里面的值赋给 v
初学者在用 ? 时,老是会犯错,例如写出这样的代码:
fn first(arr: &[i32]) -> Option<&i32> {
arr.get(0)?
}
这段代码无法通过编译,切记:? 操作符需要一个变量来承载正确的值,这个函数只会返回 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
- 唯一库包:
src/lib.rs - 默认二进制包:
src/main.rs,编译后生成的可执行文件与Package同名 - 其余二进制包:
src/bin/main1.rs和src/bin/main2.rs,它们会分别生成一个文件同名的二进制可执行文件 - 集成测试文件:
tests目录下 - 基准性能测试
benchmark文件:benches目录下 - 项目示例:
examples目录下
2、模块Module
1、路径
- 绝对路径,从包根开始,路径名以包名或者
crate作为开头 - 相对路径,从当前模块开始,以
self,super或当前模块的标识符作为开头
让我们继续经营那个惨淡的小餐馆,这次为它实现一个小功能: 文件名: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();
}
2、结构体和枚举的可见性
- 将结构体设置为
pub,但它的所有字段依然是私有的 - 将枚举设置为
pub,它的所有字段也将对外可见
3、模块与文件分离
在上述例子中,将 front_of_house 模块分离到一个单独的文件中,可以按照以下的目录结构组织代码:
markdownCopy code- src
- lib.rs
- front_of_house
- mod.rs
- hosting.rs
目录结构中有两个文件和一个文件夹:
lib.rs:保留在src目录中的原始入口文件,用于导入和公开模块。front_of_house文件夹:一个包含front_of_house模块的文件夹。mod.rs:在front_of_house文件夹中创建的mod.rs文件,用于指定子模块的公开内容。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、使用第三方包
- 修改
Cargo.toml文件,在[dependencies]区域添加一行:rand = "0.8.3" - 此时,如果你用的是
VSCode和rust-analyzer插件,该插件会自动拉取该库,你可能需要等它完成后,再进行下一步(VSCode 左下角有提示)
5、使用 {} 简化引入方式
对于下面的同时引入模块和模块中的项:
use std::io;
use std::io::Write;
可以使用 {} 的方式进行简化:
use std::io::{self, Write};
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);
}
- 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::FromStrtrait 中定义的方法。它用于将字符串解析为目标类型。在这个例子中,它被用于将经过修剪的字符串解析为usize类型。::是 Rust 中的路径分隔符,用于从命名空间、模块或类型中访问相关项。它用于引用标准库(std)中的io模块,以及该模块中的stdin函数。
这些方法是 Rust 标准库提供的常用功能,可以通过引入 std::io 和其他相关的模块来使用它们。在给定的代码中,它们是通过 use std::io; 引入了 std::io 模块,所以可以直接调用这些方法。
3、所有权
- Rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者
- 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者
- 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)
4、克隆和浅拷贝
如果我们确实需要深度复制 String 中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做 clone 的方法。
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
浅拷贝只发生在栈上,因此性能很高,在日常编程中,浅拷贝无处不在。
再回到之前看过的例子:
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);
总结:
通用的规则: 任何基本类型的组合可以 Copy ,不需要分配内存或某种形式资源的类型是可以 Copy 的。如下是一些 Copy 的类型:
- 所有整数类型,比如
u32 - 布尔类型,
bool,它的值是true和false - 所有浮点数类型,比如
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中,可以为以下结构实现方法:
- 结构体(Structs):可以为自定义的结构体类型实现方法。
rustCopy codestruct MyStruct {
// fields
}
impl MyStruct {
// methods
}
2.枚举类型(Enums):可以为枚举类型实现方法。
rustCopy codeenum MyEnum {
// variants
}
impl MyEnum {
// methods
}
3.Trait对象(Trait Objects):可以为特定的Trait对象实现方法。
rustCopy codetrait MyTrait {
// methods
}
impl MyTrait for dyn MyTrait {
// methods
}
- 原生类型(Primitive Types):可以为原生数据类型(如整数、浮点数等)实现方法,通过关联函数(Associated Functions)的形式。
rustCopy codeimpl i32 {
// methods
}
需要注意的是,Rust是一门静态类型语言,对于实现方法的类型必须在编译时就已经确定。这意味着你只能为自己定义的结构体、枚举、Trait对象等实现方法,无法为标准库或其他第三方库中的类型直接添加方法。不过可以使用特定的方式来扩展现有类型的功能,比如通过实现Trait来为现有类型添加额外的行为。Trait语法不一样,是XXforXX。
12、Rust方法和函数的区别
在Rust中,方法(Method)和函数(Function)有一些区别:
- 形式上的区别:
- 方法是关联在结构体、枚举、Trait对象等类型上的函数,使用特定的语法进行定义和调用。方法使用特定的接收者(Receiver)来指定调用方法的实例。
- 函数是独立的、不依赖于特定类型的函数,使用普通的函数定义和调用语法。
- 语法上的区别:
- 方法的定义位于结构体、枚举、Trait对象等类型的
impl块内部,并且方法的第一个参数是self(或&self、&mut self),用于表示调用方法的实例。 - 函数的定义位于模块中,没有与特定类型关联的语法要求,参数列表可以根据需求进行定义。
- 方法的定义位于结构体、枚举、Trait对象等类型的
- 使用方式上的区别:
- 方法是与特定类型关联的,只能通过实例调用。调用方法时,Rust会自动解引用或借用实例,以适应方法的接收者类型。
- 函数可以在任何地方定义和调用,不依赖于特定类型的实例。函数可以接受各种参数并返回结果。
综上所述,方法是与特定类型关联的函数,使用特定的语法和语义进行定义和调用。函数是独立的、不依赖于特定类型的函数。方法用于表示特定类型的行为和操作,而函数则用于通用的计算和逻辑操作。
13、Rust中什么时候用::调用,什么时候用.调用
在Rust中,使用::和.符号进行方法和函数的调用有以下区别:
::符号用于调用关联函数(Associated Functions)和静态方法(Static Methods)。这些函数和方法是与类型相关联的,而不是与实例相关联。关联函数是定义在impl块中的函数,而静态方法是使用static关键字定义的函数。
rustCopy codestruct MyStruct;
impl MyStruct {
fn associated_function() {
// 实现关联函数的代码
}
}
// 调用关联函数
MyStruct::associated_function();
// 调用静态方法
MyStruct::static_method();
.符号用于调用实例方法(Instance Methods)和普通函数(Regular Functions)。实例方法是定义在impl块中的方法,与特定的实例相关联。普通函数是独立的、不依赖于特定类型的函数。
rustCopy codestruct MyStruct;
impl MyStruct {
fn instance_method(&self) {
// 实现实例方法的代码
}
}
let my_instance = MyStruct;
my_instance.instance_method(); // 调用实例方法
regular_function(); // 调用普通函数
总结起来,使用::调用关联函数和静态方法,这些函数是与类型相关联的,而使用.调用实例方法和普通函数,这些函数是与特定实例或独立于特定类型的。需要注意的是,关联函数和静态方法是通过类型名来调用的,而实例方法和普通函数是通过实例或函数名来调用的。
14、闭包
Rust闭包(Closure)是一种可以捕获其环境并作为匿名函数使用的语法构造。闭包可以在需要函数的地方使用,并具有以下特性:
- 匿名性:闭包是匿名函数,没有固定的函数名称。
- 捕获环境:闭包可以捕获其周围环境中的变量和值,并在闭包体内使用。
- 可移动性:闭包可以移动(Move)捕获的变量,可以在不同的上下文中使用。
闭包的语法如下:
rustCopy code
|<参数列表>| <函数体>
<参数列表>定义了闭包的参数,可以包含零个或多个参数。<函数体>包含了闭包的实际代码,用于执行特定的操作。
下面是一个简单的例子:
fn main() {
let add_numbers = |a, b| a + b;
let result = add_numbers(5, 10);
println!("Result: {}", result);
}
在这个例子中,我们定义了一个闭包add_numbers,它接受两个参数a和b,并返回它们的和。闭包体中的代码a + b执行了实际的加法操作。
然后,我们通过调用闭包并传递参数5和10来使用闭包。闭包在这里就像一个函数调用,接收参数并返回结果。最后,我们打印了结果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
}
}
}
在上述示例中,Iterator trait 定义了一个关联类型 Item,表示迭代器产生的元素的类型。next 方法返回一个 Option<Self::Item>,其中 Self::Item 表示实现该 trait 的类型所指定的关联类型。
在 Counter 结构体的实现中,我们将关联类型 Item 定义为 u32,表示计数器产生的元素类型为无符号 32 位整数。
通过使用关联类型,我们可以使泛型代码更加灵活,允许实现者根据具体需求选择合适的类型作为关联类型的实现。
16、as做类型转换的限制,及解决方法
在 Rust 中,as 关键字用于进行简单的类型转换,将一个值转换为另一个类型。它允许在一些基本的类型之间进行转换,例如数值类型之间的转换,如 i32 到 u32 的转换。
例如,以下是使用 as 进行类型转换的示例:
let x: i32 = 5;
let y: u32 = x as u32;
在上述示例中,x 是一个 i32 类型的变量,我们使用 as 将其转换为 u32 类型,并将结果赋给 y。
然而,as 关键字有一些限制:
- 它只能用于转换 Rust 中的基本类型,如整数、浮点数、字符等。它不能用于自定义类型之间的转换。
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.");
}
}
.unwrap() 是 Rust 中用于从 Result 类型中获取值的方法。Result 是一个枚举类型,用于表示可能的操作结果,它有两个可能的值:Ok 和 Err。.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中,self和Self是两个不同的标识符,具有不同的含义和用法。
-
self(小写)是一个关键字,通常用作方法的第一个参数。它表示当前类型的实例或引用。当定义和实现方法时,self用于引用调用该方法的对象实例。它类似于其他面向对象语言中的this或self关键字。例如:
rustCopy codestruct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } }在上面的示例中,
self用于引用Rectangle结构体实例的成员变量。 -
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) { // 处理操作 } }在上面的示例中,
Self用作traitSomeTrait中关联类型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");
}
}
在上述示例中,我们使用 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中,一般以下情况会返回错误类型:
- 文件IO:打开文件、读写文件等操作可能会因为文件不存在、权限问题等原因返回错误。
- 网络通信:进行网络请求、连接服务器等操作可能因为网络问题、服务器不可用等原因返回错误。
- 解析数据:将字符串解析成数字、日期等数据类型时,如果格式不正确可能会返回错误。
- 数据转换:进行数据类型转换时,如果类型不匹配可能会返回错误。
- 动态内存分配:使用
Option或Result的方法进行动态内存分配时,可能因为内存不足等原因返回错误。 - 错误处理:手动编写的错误处理逻辑中,可能根据特定条件返回自定义的错误类型。
一般情况下,Rust推荐使用Result类型来处理可能出现错误的情况。Result类型是Rust标准库中的一个枚举类型,它有两个变体:Ok(value)表示操作成功并返回值,Err(error)表示操作失败并返回错误。通过Result类型,可以清晰地传递和处理函数执行过程中可能出现的错误情况,避免使用传统的异常处理机制。
对于返回错误类型的函数,调用者通常会使用match、if let或者?操作符来处理这些错误,根据具体情况进行恰当的错误处理和错误传播。
本文是Rust学习笔记,涵盖所有权和借用、复合类型、流程控制、模式匹配等语法知识,还介绍了泛型、特征、集合类型等特性,同时总结归纳了Option、所有权、克隆等要点,以及Rust中常见的错误返回情况。
532

被折叠的 条评论
为什么被折叠?



