theme: github
highlight: an-old-hope
观看B站软件工艺师杨旭的rust教程学习记录,有删减有补充
错误处理
- 可恢复错误:如文件未找到可再次尝试
Result<T,E>
- 不可恢复错误:如访问索引越界
panic!
宏- 打印错误信息
- 展开(unwind)清理调用栈(stack),或设置终止(abort)
- 想让二进制文件更小,可以把展开改为终止
- 在Cargo.toml中的
[profile.release]
设置panic='abort'
- 在Cargo.toml中的
展开清理调用栈会沿着栈往回走并清理每个遇到的数据
中止不进行清理直接停止程序,内存由os清理
不可恢复的错误
panic
fn main(){let v = vec![1,2,3];println!("{}",v[5]);//越界,
}
设置RUST_BACKTRACE=1
显示回溯信息,错误代码以上是我们调用的代码,错误代码以下是调用main.rs
的代码
#win
set RUST_BACKTRACE=1 && cargo run
#linux&macos
RUST_BACKTRACE=1 && cargo run
可恢复的错误
Result
在prelude
中,不需要显式导入
enum Result<T, E> {Ok(T),Err(E),
}
T
代表成功时返回的Ok
成员中的数据的类型E
代表失败时返回的Err
成员中的错误的类型
match
处理错误
use std::fs::File;
fn main() {let f = File::open("hello.txt");let f = match f {Ok(file) => file,Err(error) => {panic!("打开文件发生错误:{:?}", error)}};
}
match
匹配不同的错误
打开文件时,文件存在则返回文件内容,文件不存在则创建文件并返回文件,创建失败终止程序
use std::{fs::File, io::ErrorKind};
fn main() {let f = File::open("hello.txt");let f = match f {Ok(file) => file, //返回文件内容Err(error) => match error.kind() {ErrorKind::NotFound => match File::create("hello.txt") {Ok(fc) => fc,Err(e) => panic!("创建文件出错!{:?}", e),},other_error => panic!("打开文件出错!{:?}", other_error),},};
}
闭包改良
use std::{fs::File, io::ErrorKind};
fn main() {let _f = File::open("hello.txt").unwrap_or_else(|error| {//调用闭包if error.kind() == ErrorKind::NotFound {File::create("hello.txt").unwrap_or_else(|error| {panic!("创建错误!{:?}", error);})} else {panic!("打开错误!{:?}", error);}});
}
unwrap
Ok则返回Ok里的内容,Err则调用panic!宏
use std::fs::File;
fn main() {let f = File::open("hello.txt");let f = match f {Ok(file) => file,Err(error) => {panic!("打开文件出错!{:?}", error);}};//等价于let f = File::open("hello.txt").unwrap();
}
expect
Ok
则返回Ok
里的内容,Err
则调用panic!
宏,可以指定错误信息
use std::fs::File;
fn main() {let f = File::open("hello.txt").expect("打开文件出错!");
}
传播错误
将错误返回给调用者
use std::fs::File;
use std::io::{self, Read};
fn main() {let result = read_username_from_file();//错误信息传播到调用者println!("{:?}", &result);
}
fn read_username_from_file() -> Result<String, io::Error> {let f = File::open("hello.txt");let mut f = match f {Ok(file) => file, //成功打开文件返回文件并继续Err(e) => return Err(e), //打开失败返回错误信息};let mut s = String::new();match f.read_to_string(&mut s) {//读取文件内容Ok(_) => Ok(s), //读取成功返回信息Err(e) => Err(e), //读取失败返回错误} //返回结果
}
?
传播错误,与上面的代码功能相同,
use std::fs::File;
use std::io::{self, Read};
fn main() {let result = read_username_from_file(); //错误信息传播到调用者println!("{:?}", &result);
}
fn read_username_from_file() -> Result<String, io::Error> {let mut f = File::open("hello.txt")?;let mut s = String::new();f.read_to_string(&mut s)?;//Result为Ok,Ok()中的值就是表达式的结果,Result为Err,Err就是整个函数的返回值Ok(s)
}
链式调用
use std::fs::File;
use std::io::{self, Read};
fn main() {let result = read_username_from_file(); //错误信息传播到调用者println!("{:?}", &result);
}
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)
}
生命周期
避免悬垂引用(danging reference),x的生命周期比r短
fn main() {let r;{let x = 5;r = &x;//x离开作用域,drop x}println!("r:{}", r);//借用了一个不存在的变量
}
- Rust每个引用都有自己的生命周期
- 生命周期是引用保持有效的作用域
- 大多数情况:生命周期是隐式的、可被推断的
- 当引用的生命周期可能以不同的方式相互关联时,需要手动标注生命周期
- 实际生命周期是标注中生命周期较小的一个
//告诉借用检查器函数参数(x、y)、返回值(str)的生命周期和函数的生命周期一样
//实际生命周期是标注中生命周期较小的x
fn find_smallest<'a>(x: &'a str, y: &'a str) -> &'a str {if x < y {x} else {y}
}fn main() {let x = "apple";let y = "banana";let result = find_smallest(x, y);println!("最短的字符串: {}", result);
}
指定生命周期参数的正确方式依赖函数实现的具体功能
//返回值的生命周期只与x有关,y可以不标注
fn find_smallest<'a>(x: &'a str, y: &str) -> &'a str {x//返回x
}
fn main() {let x = "apple";let y = "banana";let result = find_smallest(x, y);println!("最短的字符串: {}", result);
}
从函数返回引用时,返回类型的生命周期参数要与其中一个参数的生命周期匹配
fn find_smallest<'a>(x: &'a str, y: &str) -> &'a str {let s1 = String::from("hello world");//返回s1的切片,也就是对s1的引用,离开函数后s1被drop,该函数返回了一个指向未知数据的引用s1.as_str()
} //drop s1
fn main() {let x = "apple";let y = "banana";let result = find_smallest(x, y);println!("最短的字符串: {}", result);
}
- 生命周期在函数方法的参数中:输入生命周期
- 生命周期在函数方法的返回值中:输出生命周期
生命周期省略规则:
- 每个引用类型的参数都有自己的生命周期
- 如果只有1个输入生命周期,那么该生命周期被赋给所有的输出生命周期
- 如果有多个输入生命周期,但其中一个参数是
&self
或&mut self
(仅方法),那么self
的生命周期会被赋给所有的输出生命周期
struct Foo<'a> {//可以不标注,在impl里标注data: &'a i32, //每个引用类型的参数都有自己的生命周期,可以不标注
}
struct Foo2 {}
impl<'a> Foo<'a> {//如果只有1个输入生命周期,那么该生命周期被赋给所有的输出生命周期fn new(data: &'a i32) -> Foo<'a> {//这个生命周期会被推断出来,可以不标注Foo { data }}//如果只有1个输入生命周期,那么该生命周期被赋给所有的输出生命周期fn get_data(&self) -> &'a i32 {//这个生命周期会被推断出来,可以不标注self.data}//如果有多个生命周期,但其中一个参数是`&self`或`&mut self`(仅方法),那么`self`的生命周期会被赋给所有的输出生命周期fn modify_data(&mut self, new_data: &'a i32) -> i32 {//可以推断返回值生命周期,但impl生命周期为'a,确保引用有效性,需要标注生命周期self.data = new_data;12 //这个返回值没有意义}//如果有多个生命周期,但其中一个参数是`&self`或`&mut self`(仅方法),那么`self`的生命周期会被赋给所有的输出生命周期fn combine_data1<'b>(&'a self, other: &'a i32) -> &i32 {//可以推断返回值的生命周期,但是要确保引用有效性,需要对参数和函数标注生命周期if *self.data > *other {self.data} else {other}}
}
impl Foo2 {//不满足推断规则需要对参数返回值标注生命周期fn combine_data2<'q>(a: &'q i32, b: &'q i32) -> &'q i32 {if a < b {a} else {b}}
}
fn main() {let x = 5;let y = 10;let mut foo = Foo::new(&x);println!("初始化的值: {}", foo.get_data()); //初始化的值: 5foo.modify_data(&y);println!("修改后的值: {}", foo.get_data()); //修改后的值: 10let combined_data1 = foo.combine_data1(&y);println!("返回最大的: {}", combined_data1); //返回最大的: 10let combine_data2 = Foo2::combine_data2(&x, &y);println!("返回最小的:{}", combine_data2); //返回最小的:5
}
struct
字段生命周期
- 在
struct
后面标注 - 在
impl
后标注 - 这些生命周期是
struct
类型的一部分
impl
块里的生命周期
- 引用必须绑定于字段引用的生命周期
静态生命周期
'static
:整个程序的持续时间
- 所有的字符串字面值都拥有
'static
生命周期
fn main() {let s1: &'static str = "hello world";//等价于let s2: &str = "hello owrld";
}