赞
踩
1、派生trait-Debug
总的来说,借用规则如下:
String
类型部分的引用对于字符串而言,切片就是对 String
类型中某一部分的引用,它看起来像这样:
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
之前提到过字符串字面量,但是没有提到它的类型:
let s = "Hello, world!";
实际上,s
的类型是 &str
,因此你也可以这样声明:
let s: &str = "Hello, world!";
该切片指向了程序可执行文件中的某个点,这也是为什么字符串字面量是不可变的,因为 &str
是一个不可变引用。
前文提到过,字符串切片是非常危险的操作,因为切片的索引是通过字节来进行,但是字符串又是 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
这里提示的很清楚,我们索引的字节落在了 中
字符的内部,这种返回没有任何意义。
追加、插入、替换、删除、链接
下面是一个示例,演示了字符和字符串的区别:
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 标量值的不同,占用的字节数会有所变化。这种变长编码有助于节省字符串占用的内存空间。
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
特征,以前学习的基本类型,都默认实现了该特征:
枚举类型是一个类型,它会包含所有可能的枚举成员, 而枚举值是该类型中的具体某个成员的实例。
不仅如此,同一个枚举类型下的不同成员还能持有不同的数据类型,例如让某些花色打印 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)数组类型包括[T;n],切片类型和数量
使用方法 | 等价使用方式 | 所有权 |
---|---|---|
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() | 可变借用 |
(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
},
}
}
例如,在以下代码中,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
直接匹配面值,如1、2、3
匹配命名变量
单分支模式 1|2 或
通过序列 …= 匹配值的范围
解构并分解值
方法是针对自己的方法,不用重复self类型
self
表示 Rectangle
的所有权转移到该方法中,这种形式用的较少&self
表示该方法对 Rectangle
的不可变借用&mut self
表示可变借用1、结构体中使用泛型
需要提前声明
x和y是相同类型
2、方法中实现泛型要定义
类似函数fn largest(list: &[T]) -> T {
该泛型函数的作用是从列表中找出最大的值,其中列表中的元素类型为 T。首先 largest<T>
对泛型参数 T
进行了声明,然后才在函数参数中进行使用该泛型参数 list: &[T]
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
返回的真实类型非常复杂时,可以用这种方法,只要返回有这个特征的类型即可,但是只能有一个具体类型。
解决方法后续提到
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
用作trait SomeTrait
中关联类型Item
的具体实现类型。它表示使用实现SomeTrait
的具体类型(在此例中是SomeStruct
)。
总结:self
是一个关键字,表示当前类型的实例或引用,用于方法参数的引用。Self
是一个特殊的类型标识符,表示当前类型本身,通常用于实现具有关联类型的trait或泛型代码中,表示使用实现这些trait或代码的具体类型。
6、特征对象的限制
不是所有特征都能拥有特征对象,只有对象安全的特征才行。当一个特征的所有方法都有如下属性时,它的对象才是安全的:
Self
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
。
1、创建
关联函数创建、宏创建
2、访问
用下标或者.get
3、迭代遍历Vector中的元素
for i in &v {
}
4、存储不同类型的元素
使用枚举类型和特征对象来实现不同类型元素的存储
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()
方法会使用 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的值
// 我是Sun… // face
/* 我 是 S u n … 哎,好长! */
以上代码有几点需要注意:
lib
类型的包中,例如 src/lib.rs
中markdown
语法!例如 # Examples
的标题,以及代码块高亮pub
对外可见,记住:文档注释是给用户看的,内部实现细节不应该被暴露出去cargo doc --open
命令,可以在生成文档后,自动在浏览器中打开网页
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!
有害状态大概分为几类:
1、如何获知变量类型或者函数的返回类型
File
,然后找到它的 open
方法VSCode
IDE 和 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>
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
目录下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
子模块导出到当前作用域中。
整体而言,这种目录结构和文件组织方式提供了更好的代码模块化和可维护性,并使代码结构更清晰易懂。
1、引入模块还是函数
从使用简洁性来说,引入函数自然是更甚一筹,但是在某些时候,引入模块会更好:
2、避免同名引用
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); }
a
模块中9、限制性语法
pub(crate)
或 pub(in crate::a)
就是限制可见性语法,前者是限制在整个包内可见,后者是通过绝对路径,限制在包内的某个模块内可见,总结一下:
pub
意味着可见性无任何限制pub(crate)
表示在当前包可见pub(self)
在当前模块可见pub(super)
在父模块可见pub(in <path>)
表示在某个路径代表的模块中可见,其中 path
必须是父模块或者祖先模块print!
将格式化文本输出到标准输出,不带换行符println!
同上,但是在行的末尾添加换行符format!
将格式化文本输出到 String
字符串与 {}
类似,{:?}
也是占位符:
{}
适用于实现了 std::fmt::Display
特征的类型,用来以更优雅、更友好的方式格式化文本,例如展示给用户{:?}
适用于实现了 std::fmt::Debug
特征的类型,用于调试场景其实两者的选择很简单,当你在写代码需要调试时,使用 {:?}
,剩下的场景,选择 {}
。
let absent_number: Option = None;
在 Rust 中,Option<T>
是一个枚举类型,用于表示一个可能存在或可能不存在的值。它有两个变体:Some(T)
和 None
。
因此,你需要使用 Option<T>
的完整写法,并通过 <i32>
显式指定 absent_number
的类型为 Option<i32>
。这样编译器就知道 absent_number
是一个可能存在或可能不存在的 i32
类型的值。
.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
模块,所以可以直接调用这些方法。
如果我们确实需要深度复制 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的不可变引用可以存在多个
可变引用只能存在一个
引用的作用域 s
从创建开始,一直持续到它最后一次使用的地方,这个跟变量的作用域有所不同,变量的作用域从创建持续到某一个花括号 }
在 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
表达式的返回值是 ()
,也就是空元组。
#![allow(unused)] 是 Rust 中的一个编译器属性(attribute),用于告知编译器允许出现未使用的代码而不发出警告。当你在 Rust 代码中定义了一个变量、函数、结构体等,但在后续的代码中没有使用它们时,编译器会发出未使用的代码的警告。
通过在代码的开头添加 #![allow(unused)],你告诉编译器不要对未使用的代码发出警告。这通常用于临时的调试或实验目的,允许你编写一些暂时未使用的代码,而不被编译器警告干扰。
然而,这个属性应该谨慎使用。未使用的代码通常是无效的或者表示了一些问题,因此编译器的警告能够帮助你发现潜在的问题并进行代码优化。因此,在实际的生产代码中,通常不建议将 #![allow(unused)] 添加到整个代码库或项目中,而是应该根据需要有选择地处理未使用的代码警告。
在 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 的借用规则和所有权模型。它使得方法可以在不同类型的对象上进行调用,而无需显式地进行引用和解引用操作。
在 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 中用于方便地初始化和填充向量。
pub
是Rust语言中的关键字,用于指定一个项(函数、结构体、枚举、方法等)对外可见或公开。它是"public"的缩写。
在Rust中,所有的项默认都是私有的,只能在定义它们的模块内部访问。如果想要在其他模块中使用这些项,就需要使用pub
关键字将其标记为公开的。标记为pub
的项可以被其他模块引用和调用。
在你提供的代码中,pub
关键字被用于定义一个公共的Summary
特性(trait)。特性是一种定义方法集合的抽象,类似于接口或协议的概念。通过使用pub
关键字,可以将该特性对外公开,允许其他模块实现该特性,并调用其中定义的方法。
具体来说,Summary
特性定义了一个抽象方法summarize
,该方法接收一个&self
参数并返回一个字符串。任何实现了Summary
特性的类型都需要提供自己的summarize
方法实现。
总结:pub
关键字用于指定一个项对外可见或公开,允许其他模块引用和使用该项。在你提供的代码中,pub
用于公开一个特性(trait),允许其他模块实现并调用其中定义的方法。
在Rust中,可以为以下结构实现方法:
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
}
rustCopy codeimpl i32 {
// methods
}
需要注意的是,Rust是一门静态类型语言,对于实现方法的类型必须在编译时就已经确定。这意味着你只能为自己定义的结构体、枚举、Trait对象等实现方法,无法为标准库或其他第三方库中的类型直接添加方法。不过可以使用特定的方式来扩展现有类型的功能,比如通过实现Trait来为现有类型添加额外的行为。Trait语法不一样,是XXforXX。
在Rust中,方法(Method)和函数(Function)有一些区别:
impl
块内部,并且方法的第一个参数是self
(或&self
、&mut self
),用于表示调用方法的实例。综上所述,方法是与特定类型关联的函数,使用特定的语法和语义进行定义和调用。函数是独立的、不依赖于特定类型的函数。方法用于表示特定类型的行为和操作,而函数则用于通用的计算和逻辑操作。
在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(); // 调用普通函数
总结起来,使用::
调用关联函数和静态方法,这些函数是与类型相关联的,而使用.
调用实例方法和普通函数,这些函数是与特定实例或独立于特定类型的。需要注意的是,关联函数和静态方法是通过类型名来调用的,而实例方法和普通函数是通过实例或函数名来调用的。
Rust闭包(Closure)是一种可以捕获其环境并作为匿名函数使用的语法构造。闭包可以在需要函数的地方使用,并具有以下特性:
闭包的语法如下:
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
。
闭包是一种灵活且强大的语法构造,可以方便地在需要函数的地方使用,捕获环境变量并执行特定的操作。闭包在函数式编程和异步编程等场景中经常使用。
关联类型(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 位整数。
通过使用关联类型,我们可以使泛型代码更加灵活,允许实现者根据具体需求选择合适的类型作为关联类型的实现。
在 Rust 中,as
关键字用于进行简单的类型转换,将一个值转换为另一个类型。它允许在一些基本的类型之间进行转换,例如数值类型之间的转换,如 i32
到 u32
的转换。
例如,以下是使用 as
进行类型转换的示例:
let x: i32 = 5;
let y: u32 = x as u32;
在上述示例中,x
是一个 i32
类型的变量,我们使用 as
将其转换为 u32
类型,并将结果赋给 y
。
然而,as
关键字有一些限制:
as
是一种静态类型转换,它在编译时确定转换是否合法,并且在运行时不会进行运行时检查。这意味着,如果类型转换不是安全的或不可行的,编译器将发出警告或错误。由于 as
的限制,当你希望在类型转换上拥有更多的控制,并且能够处理转换错误时,可以使用 TryInto
trait。
TryInto
trait 是 Rust 标准库提供的一个 trait,用于类型转换,并且允许对转换过程进行更多的控制。它提供了一个 try_into
方法,该方法尝试将一个值转换为目标类型,并返回一个 Result
类型,以便处理转换错误。
通过使用 TryInto
trait,你可以在转换过程中检查边界条件、处理转换失败等情况,并根据实际需求进行错误处理。这使得类型转换更加灵活、安全和可控。
综上所述,当你需要更多的控制和错误处理能力时,应该使用 TryInto
trait 而不是简单的 as
关键字进行类型转换。
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
类型的结果,以便更好地处理可能的错误情况,并根据实际需求进行适当的错误处理。
在 <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
。
Rust宏是Rust编程语言中的一种元编程机制,它允许在编译时进行代码生成和转换。宏是一种用于扩展Rust语法的工具,可以让开发者定义自己的代码片段,并在编译时将其插入到代码中。宏可以用于创建通用代码模板、代码重复模式的抽象以及执行其他自定义的代码转换和操作。
Rust宏有两种类型:声明式宏(Declarative Macros)和过程宏(Procedural Macros)。
声明式宏使用macro_rules!
关键字定义,它们可以匹配代码的结构并根据预定义的模式进行转换。声明式宏是基于模式匹配的,允许开发者通过模式和替换部分来指定代码的转换规则。这种宏的定义和展开是基于文本替换的。
过程宏是基于函数式宏的一种扩展,允许在编译时对代码进行更复杂的操作。过程宏接收代码作为输入,并产生新的代码作为输出。它们可以分为三种类型:自定义派生(Custom Derive)宏、属性(Attribute)宏和函数式(Function-like)宏。过程宏的定义和展开是基于Rust语法树的操作。
Rust宏的主要作用是增强语言的表达能力和编写可复用代码的能力。它们可以帮助开发者简化重复的代码、提高代码的可读性和维护性,并在编译时进行静态检查,减少运行时错误。宏在Rust中广泛用于各种领域,例如创建DSL(领域特定语言)、实现代码生成、简化数据结构的定义等。
在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
用作trait SomeTrait
中关联类型Item
的具体实现类型。它表示使用实现SomeTrait
的具体类型(在此例中是SomeStruct
)。
总结:self
是一个关键字,表示当前类型的实例或引用,用于方法参数的引用。Self
是一个特殊的类型标识符,表示当前类型本身,通常用于实现具有关联类型的trait或泛型代码中,表示使用实现这些trait或代码的具体类型。
具体来说,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 _
可以提高代码的可读性,尤其在引入大量项时,有助于减少命名冲突和提供更清晰的代码意图。
在Rust中,一般以下情况会返回错误类型:
Option
或Result
的方法进行动态内存分配时,可能因为内存不足等原因返回错误。一般情况下,Rust推荐使用Result
类型来处理可能出现错误的情况。Result
类型是Rust标准库中的一个枚举类型,它有两个变体:Ok(value)
表示操作成功并返回值,Err(error)
表示操作失败并返回错误。通过Result
类型,可以清晰地传递和处理函数执行过程中可能出现的错误情况,避免使用传统的异常处理机制。
对于返回错误类型的函数,调用者通常会使用match
、if let
或者?
操作符来处理这些错误,根据具体情况进行恰当的错误处理和错误传播。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。