伴学ChatGPT

chatting for debugging ...

background

通过网络中各种免费教程/图书学习时, 作者文字背后的思考过程无法交流...

goal

合理使用 ChatGPT 进行提示工程style 学习

trace

记录各种舒服的姿势:

CLI 测试姿势

#![allow(unused)]
fn main() {
se assert_cmd::Command;
use predicates::prelude::*;
use std::fs;

type TestResult = Result<(), Box<dyn std::error::Error>>;

#[test]
fn dies_no_args() -> TestResult {
    let mut cmd = Command::cargo_bin("echor")?; 
    cmd.assert()
        .failure()
        .stderr(predicate::str::contains("USAGE"));
    Ok(()) 
}

#[test]
fn hello1() -> TestResult {
    let expected = fs::read_to_string("tests/expected/hello1.txt")?;
    let mut cmd = Command::cargo_bin("echor")?;
    cmd.arg("Hello there").assert().success().stdout(expected);
    Ok(())
}
}

Q:

以上代码中, 为什么要专门定义 TestResult 类型?

GPT:

在 Rust 中,测试函数必须返回 Result<(), Box> 类型。这是因为测试框架需要处理测试函数中可能出现的错误,并将这些错误转换为测试结果。如果测试函数返回的是一个普通的错误类型,测试框架无法对其进行处理,因此需要使用 Result 类型。TestResult 类型只是为了更清晰地表明这是一个测试函数返回的结果类型,并没有特殊的含义。

Q:

Result<(), Box> 这一类型, 具体有什么要求?

GPT:

Result<(), Box> 是 Rust 中测试函数的标准返回类型。具体要求如下:

  • 返回类型为 Result,表示测试函数可以返回成功或失败的结果。
  • () 表示测试函数不返回任何值,因为测试函数的目的是验证代码的行为是否正确,而不是生成有用的输出。
  • Box 表示测试函数返回的错误类型可以是任何实现了 std::error::Error trait 的类型,而且这个类型被包装在 Box 中。这是因为测试框架需要能够处理各种可能的错误类型,包括编译错误、运行时错误等等。使用 Box 可以使测试函数返回任何实现了 std::error::Error trait 的错误类型,同时又能够在测试框架中方便地处理这些错误。

总之,Result<(), Box> 表示测试函数可以返回成功或失败的结果,不返回任何值,但是可以返回任何实现了 std::error::Error trait 的错误类型。

Q:

任何实现了 std::error::Error trait 的错误类型 一般有什么类型包含这种 trait? 能举例说明嘛?

GPT:

任何实现了 std::error::Error trait 的类型都可以作为测试函数返回的错误类型。在 Rust 中,标准库中的很多错误类型都实现了 std::error::Error trait,例如:

  • std::io::Error:表示 I/O 操作中可能出现的错误,如文件读写错误、网络通信错误等。
  • std::num::ParseIntError:表示将字符串转换为数字时可能出现的错误,如字符串不符合数字格式等。
  • std::env::VarError:表示获取环境变量时可能出现的错误,如环境变量不存在等。
  • serde_json::Error:表示处理 JSON 数据时可能出现的错误,如 JSON 数据格式错误等。

这些类型都实现了 std::error::Error trait,因此可以作为测试函数返回的错误类型。在测试函数中,如果某个操作出现了这些错误,可以将错误包装成 Box 类型返回,这样测试框架就可以处理这些错误并将其视为测试失败。

Q:

如果要为自定义新数据类型追加这种 trait 有哪些可行方案? 举例说明一下?

GPT:

如果要为自定义的新数据类型追加 std::error::Error trait,有以下几种可行的方案:

  • 实现 std::error::Error trait:在新数据类型的实现代码中手动实现 std::error::Error trait,包括实现 description 方法和 source 方法。这样就可以将新数据类型作为测试函数的错误类型返回。例如:
use std::error::Error;

#[derive(Debug)]
struct MyError {
    message: String,
}

impl std::fmt::Display for MyError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.message)
    }
}

impl Error for MyError {
    fn description(&self) -> &str {
        &self.message
    }

    fn source(&self) -> Option<&(dyn Error + 'static)> {
        None
    }
}

fn main() -> Result<(), Box<dyn Error>> {
    let result = Err(MyError {
        message: "An error occurred".to_owned(),
    });
    result.map_err(Box::new)?;
    Ok(())
}
  • 使用 thiserror crate:thiserror 是一个 Rust crate,它提供了一种更简单的方法来实现 std::error::Error trait。使用 thiserror crate 可以通过宏来自动生成错误类型的实现代码,包括实现 std::error::Error trait。例如:
use std::error::Error;
use thiserror::Error;

#[derive(Debug, Error)]
#[error("An error occurred: {message}")]
struct MyError {
    message: String,
}

fn main() -> Result<(), Box<dyn Error>> {
    let result = Err(MyError {
        message: "An error occurred".to_owned(),
    });
    result.map_err(Box::new)?;
    Ok(())
}

... 在上面的代码中,使用 thiserror crate 定义了一个 MyError 类型,通过 #[derive(Error)] 宏自动生成了实现 std::error::Error trait 的代码。此时,MyError 类型就可以作为测试函数的错误类型返回了。

无论使用哪种方法,一旦为新数据类型追加了 std::error::Error trait,就可以将其作为测试函数的错误类型返回了。例如,下面是使用自定义错误类型进行测试的示例:

#![allow(unused)]
fn main() {
use std::error::Error;

#[derive(Debug)]
struct MyError {
    message: String,
}

impl std::fmt::Display for MyError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.message)
    }
}

impl Error for MyError {
    fn description(&self) -> &str {
        &self.message
    }

    fn source(&self) -> Option<&(dyn Error + 'static)> {
        None
    }
}

fn foo() -> Result<(), Box<dyn Error>> {
    Err(MyError {
        message: "An error occurred".to_owned(),
    })
}

#[test]
fn test_foo() -> Result<(), Box<dyn Error>> {
    let result = foo();
    assert!(result.is_err());
    Ok(())
}
}

crate 思路

Q:

如何将一个 CLI 工具项目发布为一个 crate? 要进行哪些改造? 以及要注意什么?

GPT:

要将一个 CLI 工具项目发布为一个 crate,你需要进行以下步骤:

  • 将你的 CLI 工具项目打包成一个库。在 Rust 中,库文件通常以 lib.rs 文件为入口点,并将 CLI 代码重构为函数或结构体方法。这样可以将你的代码解耦,并使其更易于测试和重用。
  • 在你的项目根目录中创建一个 Cargo.toml 文件,其中包含以下信息:
    • 包的名称、版本号和作者信息。
    • 项目的描述信息。
    • 项目的依赖项和其版本要求。
    • 包的类型,例如库或二进制文件。
    • 包的许可证类型。
  • 将你的代码上传到 crates.io。首先,你需要创建一个 crates.io 帐户。然后,你可以使用 cargo login 命令登录并上传你的代码。在上传前,确保你的代码已经完成了测试,并且包含了合适的文档和许可证信息。
  • 给你的 crate 添加文档。Rust 社区非常注重代码文档,因此你需要确保你的 crate 包含了适当的文档和注释。你可以使用 Rust 内置的文档工具来生成文档,并将其上传到 crates.io。
  • 更新你的项目文档和 README。在将你的 crate 发布到 crates.io 后,你需要更新你的项目文档和 README 文件,以便其他开发者可以使用你的 crate。

在发布你的 crate 前,你需要注意以下几点:

  • 你需要确保你的代码质量和可靠性。这包括确保你的代码通过了单元测试和集成测试,并尽可能避免使用未经测试的依赖项。
  • 你需要确保你的代码符合 Rust 社区的代码规范和最佳实践。
  • 你需要确保你的 crate 包含了适当的许可证信息,并确保你有权发布你的代码。
  • 你需要确保你的 crate 包含了合适的文档和注释,以便其他开发者可以轻松地使用你的代码。

总之,将一个 CLI 工具项目发布为一个 crate 需要对代码进行一些改造,确保代码质量和可靠性,并且遵循 Rust 社区的最佳实践和规范。发布前,你需要确保你的代码包含了适当的文档和许可证信息,并更新你的项目文档和 README 文件。

.toml 作为配置

Q:

rudt 中读写 .toml 文件, 最推荐的 create 是哪个? 如何使用?

GPT:

如果你需要在 Rust 中读写 TOML 文件,可以使用 toml crate。这个 crate 可以在 Cargo.toml 文件中添加以下依赖来使用:

[dependencies]
toml = "0.5.8"

以下是一个简单的示例代码,演示如何使用 toml crate 读取一个名为 "config.toml" 的 TOML 文件:

use std::fs;
use toml::Value;

fn main() {
    // 读取文件
    let contents = fs::read_to_string("config.toml")
        .expect("Something went wrong reading the file");

    // 解析 TOML
    let parsed_toml: Value = toml::from_str(&contents).unwrap();

    // 访问值
    let title = parsed_toml["title"].as_str().unwrap();
    let subtitle = parsed_toml["subtitle"].as_str().unwrap();

    println!("{}: {}", title, subtitle);
}

在上面的代码中,首先使用 fs::read_to_string 函数读取 TOML 文件的内容。然后,使用 toml::from_str 函数将内容解析为 toml::Value 类型的值。接下来,可以访问解析后的 TOML 值,例如 parsed_toml["title"].as_str().unwrap() 用于获取 TOML 文件中的 "title" 值。最后,将这些值打印到控制台。

如果你需要将数据写入 TOML 文件,则可以使用 toml crate 的 to_string 函数将 toml::Value 类型的值转换为 TOML 格式的字符串,然后使用 fs::write 函数将字符串写入文件。

以下是一个简单的示例代码,演示如何使用 toml crate 写入一个名为 "config.toml" 的 TOML 文件:

use std::fs;
use toml::Value;

fn main() {
    // 创建一个 `toml::Value` 类型的值
    let mut toml_value = Value::new();
    toml_value["title"] = "My Title".into();
    toml_value["subtitle"] = "My Subtitle".into();

    // 将值转换为 TOML 格式的字符串
    let toml_str = toml::to_string(&toml_value).unwrap();

    // 写入文件
    fs::write("config.toml", toml_str).expect("Something went wrong writing the file");
}

在上面的代码中,首先创建一个 toml::Value 类型的值,该值包含要写入 TOML 文件的数据。然后,使用 toml::to_string 函数将该值转换为 TOML 格式的字符串。最后,使用 fs::write 函数将字符串写入文件。

展开 BufReader

Q:

use std::io::prelude::*;
use std::io::BufReader;
use std::fs::File;

fn main() -> std::io::Result<()> {
    let f = File::open("log.txt")?;
    let mut reader = BufReader::new(f);

    let mut line = String::new();
    let len = reader.read_line(&mut line)?;
    println!("First line is {} bytes long", len);
    Ok(())
}

如何将这段代码修改为循环将指定文件所有行内容逐一输出?

PS: 从 std::io::BufReader - Rust 抄的

GPT:

要循环输出文件的所有行内容,您可以在一个循环中使用BufReader的read_line方法。每次迭代时,该方法会读取文件中的一行,并将其存储在给定的字符串变量中。当没有更多的行可以读取时,该方法将返回一个长度为0的结果,这是跳出循环的信号。

以下是修改后的代码示例:

use std::io::prelude::*;
use std::io::BufReader;
use std::fs::File;

fn main() -> std::io::Result<()> {
    let f = File::open("log.txt")?;
    let reader = BufReader::new(f);

    for line in reader.lines() {
        println!("{}", line?);
    }
    Ok(())
}

在这个例子中,我们使用了reader.lines()方法,它返回一个std::io::Lines迭代器。每次迭代时,该迭代器都会读取文件中的一行,并将其包装在一个std::io::Result中返回。我们可以使用?操作符来解包这个结果,如果读取失败,它会将错误传播到main函数的调用方。

通过这种方式,我们可以逐行读取文件并将每一行内容输出到控制台。

Q: std::io::prelude 是什么库? 引用了什么进来? 用在哪儿了?

GPT:

std::io::prelude 是一个模块,包含了一些常见的 I/O trait 和类型的预定义导入。这些预定义导入使得开发者能够轻松地使用 Rust 标准库中的 I/O 功能,同时也减少了大量的代码重复。

在 Rust 代码中,通过引入std::io::prelude模块来使用预定义导入,通常会出现在处理 I/O 相关操作的代码中。这个模块包含了以下几个 trait 的预定义导入:

  • Read: 用于读取数据的 trait。
  • BufRead: 用于读取缓冲区的 trait。
  • Write: 用于写入数据的 trait。

这些 trait 都被用于在 Rust 中进行 I/O 操作,因此它们经常会被其他的 I/O 模块或库使用。例如,在上面提供的示例代码中,我们使用了std::io::prelude来导入Read trait,从而使用BufReader的read_line方法读取文件中的每一行内容。

A: 如何对循环中的 line 进行类似 contains 的判定呢?

GPT:

要对读取的每一行内容进行包含(contains)的判定,您可以在for循环内使用if语句,对每一行进行检查。以下是示例代码:

use std::io::prelude::*;
use std::io::BufReader;
use std::fs::File;

fn main() -> std::io::Result<()> {
    let f = File::open("log.txt")?;
    let reader = BufReader::new(f);

    for line in reader.lines() {
        let line_content = line?;
        if line_content.contains("Error") {
            println!("{}", line_content);
        }
    }
    Ok(())
}

在这个例子中,我们使用contains方法检查每一行内容是否包含字符串"Error"。如果是,我们将该行内容打印到控制台。否则,我们将跳过该行并继续读取文件的下一行。

Q:

对应代码修改为:

#![allow(unused)]
fn main() {
    let f = File::open(&args.path)
        .expect("could NOT read the file");
    let reader = BufReader::new(f);

    let mut line = String::new();
    for line in reader.lines() {
            let line_content = line?;
            if line_content.contains("Error") {
                println!("{}", line_content);
            }
        }
}

但是报错:

error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
  --> src/main.rs:66:36
   |
24 | fn main() {
   | --------- this function should return `Result` or `Option` to accept `?`
...
66 |             let line_content = line?;
   |                                    ^ cannot use the `?` operator in a function that returns `()`
   |
   = help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`

For more information about this error, try `rustc --explain E0277`.

应该如何解决?

GPT:

该错误是因为在main函数中,我们不能在循环中使用?操作符。?操作符只能用于返回Result或Option的函数或方法中,它会在发生错误时自动将错误返回给调用者。

在main函数中,我们必须手动处理错误并决定如何处理它们。在这种情况下,我们可以简单地将错误打印到控制台。以下是示例代码:

use std::io::prelude::*;
use std::io::BufReader;
use std::fs::File;
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() != 2 {
        println!("Usage: {} <path_to_file>", args[0]);
        return;
    }

    let f = File::open(&args[1]).expect("Could not read file");
    let reader = BufReader::new(f);

    for line in reader.lines() {
        if let Ok(line_content) = line {
            if line_content.contains("Error") {
                println!("{}", line_content);
            }
        } else {
            eprintln!("Error while reading file");
            return;
        }
    }
}

在这个例子中,我们使用if let语句检查读取每一行是否成功。如果成功,我们检查行内容是否包含字符串"Error",如果是则将其打印到控制台。如果读取行时发生错误,我们将错误打印到标准错误流(stderr),并退出程序。

Q:

看起来这里有多层 if 不是很 rusty, 有其它方案来达成相同效果嘛?

GPT:

您可以使用match语句来代替嵌套的if let语句,以避免多层嵌套和提高代码可读性。以下是使用match语句重写的示例代码:

use std::io::prelude::*;
use std::io::BufReader;
use std::fs::File;
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() != 2 {
        println!("Usage: {} <path_to_file>", args[0]);
        return;
    }

    let f = File::open(&args[1]).expect("Could not read file");
    let reader = BufReader::new(f);

    for line in reader.lines() {
        match line {
            Ok(line_content) => {
                if line_content.contains("Error") {
                    println!("{}", line_content);
                }
            }
            Err(_) => {
                eprintln!("Error while reading file");
                return;
            }
        }
    }
}

在这个例子中,我们使用match语句匹配line的值。如果读取行成功,我们使用Ok(line_content)分支来处理行内容,如果行内容包含字符串"Error",则将其打印到控制台。如果读取行时出现错误,我们使用Err(_)分支来处理错误,并将错误打印到标准错误流(stderr),并退出程序。

相比使用嵌套的if let语句,match语句更加清晰和简洁。

Q:

没错, match 看起来优雅多了, 不过, 还有其它方案嘛?

GPT:

是的,除了使用match语句,还有一种更简洁的方法来实现类似的效果。可以使用filter_map方法和std::result::Result::ok函数来过滤行并将它们转换为可选的字符串。然后,您可以使用for循环迭代这些字符串并将它们打印出来。以下是使用filter_map和std::result::Result::ok函数的示例代码:

use std::io::prelude::*;
use std::io::BufReader;
use std::fs::File;
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() != 2 {
        println!("Usage: {} <path_to_file>", args[0]);
        return;
    }

    let f = File::open(&args[1]).expect("Could not read file");
    let reader = BufReader::new(f);

    for line in reader.lines().filter_map(Result::ok).filter(|line| line.contains("Error")) {
        println!("{}", line);
    }
}

在这个例子中,我们首先调用filter_map方法来过滤掉那些读取行时返回错误的行,并将其转换为可选的字符串。然后,我们使用filter方法来过滤掉那些不包含字符串"Error"的行。最后,我们使用for循环迭代这些字符串,并将它们打印到控制台。

相比使用match语句和嵌套的if let语句,这种方法更加简洁和优雅。

Q:

还有更加优雅的方案嘛?

GPT: 当然,Rust 的标准库提供了非常强大的迭代器和函数式编程工具箱,可以让代码更加简洁和优雅。

在这个例子中,您可以使用filter方法和闭包来过滤掉不包含"Error"的行,然后使用map方法和unwrap函数将可选的行转换为字符串并将其打印到控制台上。这是代码的示例:

use std::io::prelude::*;
use std::io::BufReader;
use std::fs::File;
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() != 2 {
        println!("Usage: {} <path_to_file>", args[0]);
        return;
    }

    let f = File::open(&args[1]).expect("Could not read file");
    let reader = BufReader::new(f);

    reader.lines()
          .filter(|line| line.as_ref().map(|s| s.contains("Error")).unwrap_or(false))
          .map(|line| line.unwrap())
          .for_each(|line| println!("{}", line));
}

在这个例子中,我们首先调用filter方法来过滤掉那些不包含字符串"Error"的行。由于lines方法返回的是一个包含Result<Option>的迭代器,我们需要在闭包中使用as_ref方法来将Result<Option>转换为Option<&String>。然后,我们使用map方法和unwrap函数来将可选的行转换为字符串,并将其打印到控制台上。

使用这种方法,我们可以将代码进一步简化,并更加清晰地表达我们的意图。

Q:

没错, 这样又简洁,又整齐,而且意义明确; 看起来 迭代器和函数式工具有很多, 类似 filter 和 map 的内置工具还有哪些? 分别有什么优雅的实用案例?

GPT:

Rust 的标准库提供了丰富的迭代器和函数式编程工具,包括:

  • map:将一个迭代器中的每个元素转换为另一种类型的元素,通常用于数据类型转换和计算。
    • 示例:
#![allow(unused)]
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let squares: Vec<i32> = numbers.iter().map(|n| n * n).collect();
// squares is [1, 4, 9, 16, 25]
}
  • filter:根据某个条件过滤出迭代器中的元素,通常用于数据过滤和筛选。
    • 示例:
#![allow(unused)]
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let evens: Vec<i32> = numbers.iter().filter(|n| n % 2 == 0).collect();
// evens is [2, 4]
}
  • fold:使用某种累加器(初始值)和一个闭包来聚合迭代器中的元素,通常用于求和、平均值和字符串拼接等操作。
    • 示例:计算一个数字向量中所有元素的和。
#![allow(unused)]
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let sum: i32 = numbers.iter().fold(0, |acc, n| acc + n);
// sum is 15
}
  • flat_map:将一个迭代器中的每个元素转换为另一个迭代器,然后将所有的迭代器合并为一个迭代器。
    • 示例:
#![allow(unused)]
fn main() {
let words = vec!["hello world", "foo bar", "baz qux"];
let letters: Vec<char> = words.iter().flat_map(|s| s.chars()).collect();
// letters is ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 'f', 'o', 'o', ' ', 'b', 'a', 'r', 'b', 'a', 'z', ' ', 'q', 'u', 'x']
}
  • any 和 all:检查迭代器中是否存在任意元素或所有元素都满足某个条件。
    • 示例:
#![allow(unused)]
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let any_odd = numbers.iter().any(|n| n % 2 == 1);
let all_even = numbers.iter().all(|n| n % 2 == 0);
// any_odd is true, all_even is false
}
  • enumerate: 枚举迭代器中的元素,并将元素索引和值组成一个元组返回。
    • 示例:遍历一个字符串向量并打印每个元素的索引和值。
#![allow(unused)]
fn main() {
let v = vec!["foo", "bar", "baz"];
for (i, s) in v.iter().enumerate() {
    println!("{}: {}", i, s);
}

}
  • zip:将两个迭代器合并为一个元素对的迭代器。
    • 示例:从两个向量中取出对应位置的元素并组成一个新的向量
#![allow(unused)]
fn main() {
let numbers = vec![1, 2, 3];
let letters = vec!['a', 'b', 'c'];
let pairs: Vec<(i32, char)> = numbers.iter().zip(letters.iter()).collect();
// pairs is [(1, 'a'), (2, 'b'), (3, 'c')]

}

当使用迭代器处理数据时,有时需要对数据进行分段处理。在这种情况下,可以使用 take 和 skip 方法。

  • take:从一个集合中取出前几个元素。例如,从一个包含所有人的向量中取出前 10 个人。
    • take(n) 方法会创建一个新的迭代器,它只包含原始迭代器的前 n 个元素。例如:
#![allow(unused)]
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let first_three = numbers.iter().take(3);
for number in first_three {
    println!("{}", number);
}
// 输出:
// 1
// 2
// 3
}
  • skip:跳过一个集合中的前几个元素。例如,从一个包含所有人的向量中跳过前 10 个人,然后返回剩余的所有人。
    • skip(n) 方法会创建一个新的迭代器,它跳过原始迭代器的前 n 个元素,然后迭代其余的元素。例如:
#![allow(unused)]
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let last_two = numbers.iter().skip(3);
for number in last_two {
    println!("{}", number);
}
// 输出:
// 4
// 5

}
  • chain:将两个集合连接在一起,然后返回一个新的集合。例如,将一个包含所有男性的向量和一个包含所有女性的向量连接在一起,然后返回一个包含所有人的向量。
    • 另一个常见的迭代器方法是 chain 方法。它接收另一个迭代器作为参数,将其添加到当前迭代器的末尾,并返回一个新的迭代器。例如:
#![allow(unused)]
fn main() {
let numbers1 = vec![1, 2, 3];
let numbers2 = vec![4, 5, 6];
let all_numbers = numbers1.iter().chain(numbers2.iter());
for number in all_numbers {
    println!("{}", number);
}
// 输出:
// 1
// 2
// 3
// 4
// 5
// 6

}

在上面的示例中,我们首先创建了两个向量 numbers1 和 numbers2,然后将它们合并为一个新的迭代器 all_numbers。最后,我们遍历 all_numbers 并输出其中的所有元素。

这些是 Rust 中一些常见的迭代器方法和函数式工具,您可以根据自己的需要来选择使用它们。在 Rust 中,迭代器和函数式编程被广泛应用于各种场景,例如处理集合、过滤和排序数据、处理文本等等。通过使用这些工具,您可以编写更加简洁、优雅、高效和安全的代码。

Q:

发现还有 try_fold 之类带有 try_ 的迭代器,一般是用在什么场景中的?

GPT:

try_fold 方法是一个带有 try_ 前缀的迭代器方法,它类似于 fold 方法,但是它允许在迭代器处理过程中发生错误,并将错误传递给调用方进行处理。try_fold 方法的使用场景通常是处理可能会失败的操作的迭代器元素序列,例如网络请求或文件 I/O 操作。

try_fold 方法有两个参数:一个初始累加器和一个闭包,闭包将当前累加器的值和迭代器的下一个元素作为参数,并返回一个 Result 值。如果闭包返回一个 Ok 值,则 try_fold 方法会继续迭代。如果闭包返回一个 Err 值,则 try_fold 方法会立即停止迭代并将错误传递给调用方。

以下是一个简单的示例,演示如何使用 try_fold 方法来计算向量中所有元素的和,其中每个元素都是 Result<i32, String> 类型:

fn main() {
    let vec = vec![Ok(1), Ok(2), Err("failed".to_string()), Ok(3)];
    let sum = vec
        .into_iter()
        .try_fold(0, |acc, x| x.map(|n| acc + n));
    match sum {
        Ok(result) => println!("Sum is {}", result),
        Err(error) => println!("Error: {}", error),
    }
}

在上面的代码中,我们首先创建了一个向量 vec,其中包含三个 Ok 值和一个 Err 值。然后我们使用 into_iter 方法将向量转换为迭代器,使用 try_fold 方法来计算所有元素的和,其中使用 map 方法将 Ok 值映射到它们的整数值,并忽略 Err 值。最后,我们使用 match 来处理可能的错误和结果。

refer.

其实官方的文档吃透, 基本可以平趟 80% 的日常开发了...

logging

版本记要

  • ..
  • 230224 ZQ init.
          _~^~`~_
      () /  - ◶  \ \/
        '_   ⏝   _'
        > '--+--' )

...act by ferris-actor v0.2.4 (built on 23.0303.201916)

知识共享许可协议 本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可;-)