Crates.io | myest |
lib.rs | myest |
version | 0.1.0 |
source | src |
created_at | 2023-03-29 06:11:49.935812 |
updated_at | 2023-03-29 06:11:49.935812 |
description | nothing but funny |
homepage | |
repository | |
max_upload_size | |
id | 823716 |
size | 56,635 |
0.使用rustup update来升级rust
1.通常使用cargo来构建项目
1)cargo build用来构建代码,cargo check用来检查代码能否通过编译
2)cargo run用来构建并运行
3)cargo构建的target放在target/debug目录下,cargo build --release在优化模式下构建代码,生成的文件放在target/release目录下
4)cargo update可以强制更新依赖包,这个指令会忽略Cargo.lock文件,后者是为了确保构建是可重现的。
5)cargo doc --open可以在本地构建一份有关所有依赖的文档,但是没有出现以来库的文档?
2.crate代表了一系列源代码文件的集合
1)分为二进制包(binary crate)和库包(library crate)
3.为了保证安全的写出复杂甚至并行得代码,Rust默认变量是不可变的。
1)变量和常量的区别在于,变量用let关键字声明,常量用const关键字声明,同时声明时必须显示的标注值得类型,如const MAX_POINTS: u32 = 100_000;。
常量可以声明在全局作用域中,只能将常量绑定到一个常量表达式上,而无法将一个函数得返回值,或者其他需要在运行时计算的值绑定到常量上。
2)连续使用let关键字可以隐藏前面的变量,它不同于mut,前者可以保持变量的不可变性。使用let隐藏也可以达到使用相同变量名代表不同类型值得用途。
3)整数字面量89_222(Decimal),0xff(Hex),0o77(Octal),0b1111_0000(Binary),b'A'(Byte)
4)char类型占用四个字节,是Unicode标量值。
5)数据类型分为标量类型和符合类型。标量类型就是内置得数据类型,复合类型可以将多个不同类型得值组合为一个类型。Rust提供两种复合类型,元组和数组。
4.函数得参数必须显示声明类型
1)函数体中可以使用语句或者表达式,语句指的是那些执行操作但是没有返回值得指令,而表达式是会进行计算并产生一个值作为结果得指令。
2)let y = 6;是一条语句,x + y是表达式(注意没有分号)。调用函数,调用宏,花括号{}都是表达式。
3)函数如果只有语句,没有表达式返回,会默认返回一个空元组()
5.控制流
1)if后面接得表达式必须产生一个bool类型得值,if else else if
2)Rust提供三种循环 loop while for。loop可以返回值,如break 2;会返回2。while用于条件循环,for用于遍历数据。
3)(1..4)表示[1,2,3,4],rev()表示倒叙。iter()表示迭代。
6.Rust的所有权 !!!!
1)栈中的数据必须拥有一个已知且固定的大小,那些在编译期无法确定大小的数据,只能存储在堆中。
2)所有权规则:1.Rust中的每一个值都有一个对应的变量作为它的所有者。2.在同一时间内,值有且仅有一个所有者。3.当所有者离开自己的作用域时,它持有
的值就会被释放掉。引用是没有所有权的。
3)存在栈上的数据不需要所有权,存在堆上的数据才需要所有权。
4)Rust使用移动(move)来代替浅拷贝与深拷贝,因为移动会使之前的变量变为无效!Rust永远不会自动地创建数据的深拷贝。
5)如果一个类型实现了Copy trait,那么这种类型在给其他变量赋值后可以保证可用性,不用移动。如果一个类型实现了Drop trait,那么Rust就不允许这个
类型再实现Copy trait。默认拥有Copy trait的类型一般有整形,bool,char,浮点型,包含以上的元组。
6)将值传递给函数在语义上类似于对变量进行赋值。
7)引用允许在不获取所有权的前提下使用值。引用&的相反操作叫做解引用*。
8)为了避免数据竞争data race,可变引用只允许在特定作用域中声明一个。不能在拥有不可变引用的同时创建可变引用。同时存在多个不可变引用则是合理的。
总结就是,在任何一段给定的时间里,要么只有一个可变引用,要么有多个不可变引用。引用总是有效的。
9)切片也不会获取数据所有权。字符串类型为String,字符串切片的类型为&str。数组也同样可以使用切片。
7.结构体 !!!!
1)结构体与元组相似,区别在于结构体中每个数据必须要有名字,而不再需要向元组一样通过索引来访问数据。
2)元组结构体可以省略字段的名字,如struct color(i32, i32, i32).
3)print打印不了自定义结构体,需要实现Display trait。
4)关联函数是不接收self的方法。
5)可以使用多个impl块。
8.枚举类型 !!!!
1)枚举可以关联相应的数据
2)直接将数据附加到枚举的每个变体中,就可以不额外使用结构体了。每个变体可以拥有不同类型和数量的关联数据。
3)个人理解,有点儿基类的意思,但是与box的区别是什么呢???
4)枚举也可以impl
5)match表达式是用来处理枚举的控制流结构。整个match表达式有返回值。match匹配必须穷举所有的可能!当不需要处理所有可能的值时,使用_通配符来代替其余的值。
6)在只关心一种值的情况下,可以使用if let简单控制流。if let可以搭配else。
9.管理项目,包,单元包及模块
1)一个包可以包含任意多二进制单元包和最多一个库单元包。src/main.rs为二进制单元包,src/lib.rs为库单元包。如果想要多个二进制单元包,需要在src/bin下创建来
添加多个二进制单元包。
2)模块决定了一个条目是公有的还是私有的。以mod关键字开头来定义一个模块,模块内可以继续定义模块.Rust开发者倾向于使用绝对路径来引用模块。
3)Rust中的所有条目默认都是私有的。(条目包括结构体,函数,方法,枚举,常量和模块)。父模块默认无法使用子模块的私有条目,但是子模块可以使用所有祖先模块中
的条目。
4)可以使用super来指代父模块。
5)struct设置为公有后,字段也需要设置公有才能对外使用。enum设置为公有后,所有变体都为公有。
6)使用use关键字可以将路径path引入作用域,简化调用。使用相对路径引入时必须使用self开始,但是不建议使用!
7)使用as关键字来提供新的名称,use std::io::Result as IoResult,可以避免同名冲突问题。
8)使用pub use关键字可以将名称重导出,可以将use的路径在模块中使用,也可以被外部代码使用。pub mod可以将嵌套的模块导出!
9)使用嵌套路径可以清理众多use语句,如use std::{cmp::Ordering, io};use std::io::{self, Write};
10)可以将模块拆分成不同的文件,在mod front_of house后使用分号而不是代码块会让Rust前往与当前模块同名的文件中加载模块内容!
10.通用集合类型
1)使用vec想要存储不同类型的数据,可以使用enum枚举,再通过match匹配枚举中的值。如果无法穷举所有枚举,就要使用动态trait类型。
2)使用format!可以格式化字符串,format!("{}-{}-{}", s1, s2, s3);这不会获取任何参数的所有权。
3)字符串不可以通过索引来获取字符,因为Rust中字符是按照UTF-8存的。但是可以使用&s[0..4]这种范围来获取字符,但是当获取失败时会panic。
通常的取字符串的方法是,使用chars方法,或者bytes方法。chars返回字符,bytes返回原始字节!
4)HashMap的insert会覆盖之前的值,可以使用entry.or_insert来判断是否有值并且在没有值的时候插入。or_insert和entry为我们返回一个&mut v,这样就可以
修改值了!需要使用*解引用才能修改!
5)哈希函数可以抵御拒绝服务攻击DDOS,哈希函数为了安全性付出了一些性能代价。可以通过实现BuildHasher trait来修改哈希函数。
11.错误处理
1)Rust将错误分成两类:可恢复错误和不可恢复错误。可恢复错误例如文件未找到,不可恢复错误例如Bug。可恢复错误类型未Result<T, E>,不可恢复错误状态
时需要panic!。
2)panic会让Rust沿着调用栈的反向顺序展开并遍历所有调用函数,并依次清理数据,但这需要在二进制文件中存储额外信息。也可以选择立即终止程序,在
Cargo.toml中的[profile]区域添加panic = 'abort'。如果只想在发布模式下则[profile.release]panic = 'abort'。
3)失败时触发panic!可以使用Result的快捷方法unwrap和expect来代替match表达式。当结果未Ok时,unwrap返回内部值,当结果未error时,直接panic!。
expect则允许在unwrap的基础上,指定panic!信息。
4)当有错误产生时,除了直接处理这个错误,还可以将错误返回给调用者,这叫做错误传播。当我们不确定接口返回值处理逻辑时,就可以选择错误传播的做法,让
调用者自行处理。使用问号运算符?可以简化这个语法。?运算符放在Result之后,意思是假如返回结果为Ok则会返回Ok中值并继续执行,假如结果为Err则返回。如:
pub fn read_username_from_file_new() -> Result<String, io::Error>{
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
5)?运算符会隐式的通过From trait将错误转换成当前函数返回类型的错误。?运算符只能用于返回Result的函数!
6)?运算发可以进行链式调用,如:File::open("hello.txt")?.read_to_string(&mut s)?;
7)需要经验总结什么时候使用panic!,什么时候处理Result!可以通过定义结构体来检测数据有效性!
12.泛型,trait及生命周期
0)和C++一样,泛型参数如
}
5)返回值也可以返回trait类型,类似参数约束的语法糖。如: fn returns() -> impl Summary{};但是这种写法无法返回可能会有多个类型的返回值!
6)trait约束可以有条件的实现方法。如:impl<T: Display + PartialOrd>Pair<T>{},也可以为实现了某个trait的类型有条件的实现另一个trait,这叫做
覆盖实现,如:impl<T: Display> ToString for T{},这会使任何实现了Display trait的类型都实现了ToString trait,从而可以使用to_string()方法!
7)Rust的每个引用都有自己的生命周期,它对应着引用保持有效性的作用域。&i32, &'a i32, &'a mut i32.标注生命周期是为了向Rust描述多个泛型生命周期
参数之间的关系!比如有两个参数的函数,参数1的生命周期和参数2的生命周期都是'a,意味着参数1和参数2的引用必须与这里的泛型生命周期存活一样长的时间,
函数返回的引用的生命周期与传入的引用的生命周期中较短的那一个相同。
8)生命周期语法就是用来关联一个函数中不同参数及返回值的生命周期的。一旦它们形成了某种联系,Rust就获得了足够的信息来支持保障内存安全的操作,并
阻止那些可能会导致悬垂指针或其他违反内存安全的行为!
9)可以在结构体中存储引用,但是需要为结构体定义中的每一个引用都添加生命周期标注。
10)生命周期三条省略规则:1.为每个输入参数生成一个生命周期。2.当只有一个输入,输出生命周期与输入生命周期相同。3.若有多个输入生命周期参数,而其中
一个是&self或&mut self,将self生命周期赋给输出。若三个步骤后所有引用都有生命周期了,则改方法或函数可以使用省略规则。
11)所有的字符串字面量都拥有'static 静态生命周期,表示整个程序的执行期。如:let s: &'static str = "i have a static lifetime!"。
13.自动化测试 1)虽然测试可以高效地暴露程序中的Bug,但是在证明Bug不存在方面却无能为力。使用自动化测试来尽量防止错误。 2)Rust测试就是一个标注又test属性的函数!属性(Attribute)是一种用于修饰Rust代码的元数据。只需要将#[test]添加到关键字fn的上一行便可以将函数 变为测试函数。 3)编写完测试函数,可以使用cargo test命令来运行测试。这个命令会构建并执行一个用于测试的可执行文件,该文件在执行的过程中会逐一调用所有标注了test 属性的函数,并生成统计测试运行成功或失败的相关报告。cargo test生成的二进制文件默认会并行执行所有的测试,并截获测试运行过程中产生的输出来让测试 结果相关的内容更加易读。 4)should_panic属性,标记了这个属性的函数会在代码发生panic时顺利通过,而在代码不发生panic时执行失败。在should_panic中添加expected参数,可以 让测试更加精细一些,它会检查panic发生时输出的错误提示信息是否包含了expected中包含的文字。 5)可以通过返回Result<T, E>来编写测试,此时不能使用should_panic,返回Err即为失败。 6)可以让测试串行的执行,只需要设置测试线程为1即可,cargo test -- --test-threads=1 7)当测试通过时,标准输出是打印不出来的,使用命令cargo test -- --nocapture可以在测试通过时也把输出打出来。 8)通过指定函数名,可以指定测试的函数 cargo test add 可以测试包含add的所有函数。 9)使用#[ignore]属性可以在cargo test的时候不运行这个函数,同时,可以使用cargo test -- --ignored指令来运行带有ignore属性的函数。ignore函数 往往是耗时的,我们可以在充足时间时运行命令单独运行ignore函数。 10)单元测试需要在测试模块(一般时tests mod)上标注#[cfg(test)],而集成测试不需要,因为单元测试和模块代码在同一个编译文件中,集成测试是单独文件。 11)集成测试完全位于代码库之外,首先创建一个tests目录,cargo会在这个目录下找测试文件,每个文件都处理为一个独立的包。cargo test --test XXXX 可以单独运行特定集成测试文件下的所有测试函数。通过在tests\common\下创建rs文件,可以提供集成测试文件之间共享得一些代码,不再视为集成测试文件。 14.闭包和迭代器 1)Rust中的闭包是一种可以存入变量或作为参数传递给其他函数的匿名函数。闭包可以从定义它的作用域中捕获值。(类似C++中的lambda表达式). 2)闭包不强制要求标注参数和返回值的类型,函数因为是要暴漏给用户的显示接口的一部分,所以需要标注好类型,但是闭包并不会暴漏接口。闭包通常相对短小, 只在狭窄的上下文中使用,不会广泛应用,编译器可以推导出来类型。但是你仍然可以未闭包手动添加类型。|num: u32| -> u32 {} 3)所有的闭包都至少实现了Fn,FnMut及FnOnce中的一个trait。就算是参数和返回值相同的闭包,他们的类型也是不一样的,因此想在结构体/枚举或者函数参数 中使用闭包,需要用到泛型及trait约束。函数也同样使用了这三个Fn trait。 4)FnOnce代表闭包可以从它的封闭作用域中,取得使用的变量的所有权并将他们移动至闭包中。FnMut代表可以从环境中可变的借用值并对他们修改。Fn代表从环境 中不可变的借用。如果需要强制闭包获取环境中的值得所有权,可以使用move闭包,就是在闭包前面加上move,这在将捕获得变量移动至新线程中去是很有用的。 5)迭代器是惰性的layzy,除非主动调用方法来消耗并使用迭代器,否则他们不会产生任何的实际效果。所有迭代器都实现了Iterator trait和next方法。 6)iter方法生成的是一个不可变引用的迭代器,用过next取得的值是指向动态数组中各个元素的不可变引用。如果想取得动态数组所有权并返回元素本身,用into_iter方法 如果需要可变引用的迭代器,使用iter_mut方法。 7)通过使用迭代器适配器,可以将迭代器转换成其他不同类型的迭代器。如let v2: Vec<_> = v1.iter().map(|x|x+1).collect(); 因为迭代器是惰性的,调用map之后不会产生任何效果,所以需要调用collect将迭代器消耗,重新生成动态数组。 8)熟悉filter,zip,map,sum等消耗迭代器的方法,这些方法都是基于next实现的,称为消耗适配器。 9)尽管迭代器是一种高层次抽象,但它在编译后生成了与手写底层代码几乎一样的产物。迭代器是一种零开销抽象。Rust会努力实现零开销! 15.进一步学习cargo 1)再toml文件中使用[profile.dev]和[profile.release]来对发布和构建进行配置,opt-level是优化等级0-3.cargo build默认使用dev配置编译程序, cargo build --release使用release配置编译程序。 2)可以使用"///"来编写文档注释,在文档注释中可以使用Markdown语法("https://markdown.com.cn/basic-syntax/")来格式化内容。文档注释可以用作 代码测试。"//!"注释可以为包含这个注释的外层条目(而不是紧随注释之后的条目)添加文档,这种注释通常被用在包的根文件lib.rs或模块的根文件上,分别 为整个包或整个模块提供文档。 3)pub use可以重新导出部分条目,建立一套和你的内部结构不同的对外结构!当开发者内部有多个mod嵌套时,使用这个技巧导出接口或类型很有用!