Rust简明教程第六章-错误处理&生命周期


theme: github
highlight: an-old-hope

观看B站软件工艺师杨旭的rust教程学习记录,有删减有补充

错误处理

  • 可恢复错误:如文件未找到可再次尝试
    • Result<T,E>
  • 不可恢复错误:如访问索引越界
    • panic!
    • 打印错误信息
    • 展开(unwind)清理调用栈(stack),或设置终止(abort)
  • 想让二进制文件更小,可以把展开改为终止
    • 在Cargo.toml中的[profile.release]设置panic='abort'

展开清理调用栈会沿着栈往回走并清理每个遇到的数据
中止不进行清理直接停止程序,内存由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

可恢复的错误

Resultprelude中,不需要显式导入

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()); //初始化的值: 5

    foo.modify_data(&y);
    println!("修改后的值: {}", foo.get_data()); //修改后的值: 10

    let combined_data1 = foo.combine_data1(&y);
    println!("返回最大的: {}", combined_data1); //返回最大的: 10

    let 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";
}

相关推荐

  1. Rust简明教程-错误处理&生命周期

    2024-07-11 19:42:03       26 阅读
  2. React 生命周期

    2024-07-11 19:42:03       35 阅读
  3. Rust 生命周期

    2024-07-11 19:42:03       51 阅读
  4. Rust】——生命周期

    2024-07-11 19:42:03       30 阅读
  5. 十九 SOAP 错误处理

    2024-07-11 19:42:03       28 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-11 19:42:03       66 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-11 19:42:03       70 阅读
  3. 在Django里面运行非项目文件

    2024-07-11 19:42:03       57 阅读
  4. Python语言-面向对象

    2024-07-11 19:42:03       68 阅读

热门阅读

  1. 【Django】Django 使用连接串配置数据库

    2024-07-11 19:42:03       22 阅读
  2. Sass 和 SCSS

    2024-07-11 19:42:03       19 阅读
  3. 系统迁移从CentOS7.9到Rocky8.9

    2024-07-11 19:42:03       23 阅读
  4. 深入理解CSS中的块格式化上下文(BFC)

    2024-07-11 19:42:03       21 阅读
  5. EdgeOne安全能力开箱测评挑战赛

    2024-07-11 19:42:03       24 阅读
  6. mysql 8.0.37 客户端在centos7安装顺序

    2024-07-11 19:42:03       22 阅读
  7. 【C++】include头文件中双引号和尖括号的区别

    2024-07-11 19:42:03       17 阅读
  8. 在 MyBatis-Plus 中,字段更新为 null 的方法

    2024-07-11 19:42:03       17 阅读
  9. html基础-持续更新

    2024-07-11 19:42:03       23 阅读