Skip to content
📖 Auto 编程语言(主书)本书目录

项目:文件处理器 ​

本章是第二阶段的高潮项目。我们将构建一个完整的命令行文件处理器——一个 读取文本文件、搜索匹配模式的行并报告结果的小工具。在此过程中,我们将 综合运用第二阶段的所有概念:自定义类型、枚举、!T 错误处理、文件 I/O 和泛型。

我们的文件处理器将结合:

  • 自定义类型(第 10 章)—— 用于程序设置的 Config 类型
  • 枚举与模式匹配(第 11 章)—— 建模结果
  • !T 错误处理(第 12 章)—— 使用 bang 类型传播和处理错误
  • 文件 I/O(第 12 章)—— 将文件内容读取为字符串
  • 泛型(第 13 章)—— 使用类型参数和高阶函数编写可复用的搜索

项目概述 ​

我们要构建的是一个名为 fileproc 的命令行工具,它接受输入文件路径、 输出文件路径和一个详细输出标志。它读取输入文件,逐行搜索单词 "hello",统计匹配数量,并可选地打印每一匹配行。

到本章结束时,程序将能够:

  1. 将命令行参数解析为 Config 类型
  2. 验证配置,拒绝无效输入
  3. 以正确的错误处理方式读取文件内容
  4. 使用泛型高阶函数搜索行
  5. 以可选的详细模式输出结果

我们将逐步构建,每次添加一个概念。

解析参数 ​

任何命令行程序的第一步都是收集用户配置。与其将独立的变量散布在代码 各处,不如将所有设置归入一个 Config 类型。这让代码更易读、更易传递、 也更容易扩展。

用于命令行参数的 `Config` 类型和 `parse_args` 函数

输出:

Input: input.txt
Output: output.txt

Config 类型有三个字段:两个字符串类型的文件路径和一个布尔类型的详细 模式标志。parse_args 函数用默认值构建一个 Config。在真实程序中,这个 函数会读取 env.args() 来获取命令行参数——目前我们使用硬编码默认值以 保持简单。

通过点号访问字段:config.input_path、config.verbose。在 Rust 中,这 直接映射到结构体字段访问。在 C 中,是通过结构体指针的相同操作。在 Python 和 TypeScript 中,这些成为类属性。

这种模式——将相关值归入一个类型——是 Auto 中最重要的组织工具之一。随着 程序的增长,你可以向 Config 添加新字段,而无需改变它的传递方式。

读取文件 ​

有了配置后,我们需要读取文件。文件操作可能失败:文件可能不存在、用户 可能没有权限、磁盘可能已满。Auto 使用 !T(Result)类型来处理这种情况。

使用 `!String` 错误处理读取文件

输出:

Hello from file!

read_file 函数返回 !String,这是 Auto 的 Result 类型。! 前缀表示 "此操作可能失败。"成功时,它返回文件内容作为 String。失败时,它返回 错误信息。

调用者使用 !(后缀 bang 操作符,等价于 Rust 的 ?)解包结果。如果 read_file 失败,错误会立即向上传播。如果成功,content 将文件内容作为 普通 String 持有。

这与第 12 章中看到的模式相同。与 Python 或 JavaScript 等语言的 关键区别在于,Auto 强制 你承认失败的可能性。你不可能意外忽略错误—— 编译器要求使用 ! 解包。

在 C 中,转译器生成一个带 is_ok 标志的标签联合(ResultString)。 调用者必须在访问值之前检查标志。这正是 Auto 在内部所做的,但编译器会 自动处理。

验证配置 ​

在处理之前,我们应该验证配置。空的输入路径毫无意义——我们应该尽早拒绝 并提供清晰的错误信息。这就是 !T 真正闪光的地方:验证错误通过相同的 机制传播,就像 I/O 错误一样。

使用 `!Config` 返回类型验证 `Config`

输出:

data.txt

validate 函数返回 !Config——验证通过时返回相同的配置,验证失败时返回 错误字符串。在 Auto 中,函数体中的最后一个表达式就是返回值。if/else 表达式在错误分支返回 "input path cannot be empty",在成功分支返回 c (原始配置)。

这是一个强大的模式。返回类型 !Config 意味着函数可以成功返回 Config 或失败返回 String。调用者使用 ! 解包,传播任何错误。因为 validate 和 read_file 都返回 !T,它们自然地链接在一起:调用栈中的每个 ! 都 会将错误向上传播,直到 main 处理它们。

跨语言映射:

  • Rust 使用 Result<Config, String> 和 Ok(c)/Err(msg)。? 操作符 解包并传播错误。
  • Python 抛出 ValidationError 异常。没有编译时强制处理的要求。
  • C 使用带手动 is_ok 检查的标签联合 ResultConfig。
  • TypeScript 抛出 Error。与 Python 类似,错误处理是可选的。

泛型搜索 ​

现在我们来到处理器的核心:搜索行。我们想要一个可复用的函数,它接受一 个字符串列表和一个谓词(决定某行是否匹配的函数),并返回匹配的结果。

这是泛型与高阶函数结合的完美用例。搜索逻辑是相同的,不管谓词返回什么 ——它可以返回布尔值、过滤后的字符串、计数或任何其他东西。

带高阶谓词的泛型 `search_lines` 函数

输出:

hello world
hello auto

让我们拆解关键部分:

  1. fn search_lines<T> —— 函数对类型 T 是泛型的。谓词决定 T 是 什么。

  2. predicate fn(String) T —— 第二个参数本身是一个函数。它接受一个 String 并返回一个 T。这是一个_高阶函数_——接受另一个函数作为参数 的函数。

  3. 匿名函数 —— 谓词是内联定义的: fn(s String) ?String { ... }。这是 Auto 的匿名函数语法(类似于 Rust 闭包或 JavaScript 箭头函数)。返回类型 ?String 意味着它返回匹配的 字符串或 none。

  4. s.contains("hello") —— 检查子字符串是否存在的字符串方法。

谓词返回 ?String(Option)。当行包含 "hello" 时,它返回 s(行本身)。 否则返回 none。search_lines 函数将所有非 none 的结果收集到输出列表 中。

这个设计非常灵活。你可以传入不同的谓词来计数匹配项、提取电子邮件地址、 查找超过 80 个字符的行或任何其他操作——所有这些都不需要更改 search_lines。泛型类型参数 T 确保函数能与谓词产生的任何返回类型一起 工作。

在 Rust 中,这映射为闭包(|s: &String| -> Option<String>)。在 Python 中,是 lambda。在 TypeScript 中,是箭头函数。在 C 中,转译器为每个谓词 生成具体函数,因为 C 没有闭包。

完整的文件处理器 ​

现在我们将所有内容组合在一起。完整的文件处理器将 Config 类型、带错误 处理的文件读取、行迭代和详细输出合并为一个简洁的程序。

完整的文件处理器——结合类型、错误处理和文件 I/O

输出:

Found: hello world
Found: hello auto
Found 2 matches

让我们跟踪执行过程:

  1. main 创建一个 verbose: true 的 Config 并调用 process。
  2. process 调用 read_file,返回模拟的文件内容。! 解包传播任何 错误。
  3. content.split("\n") 将字符串拆分为行数组。
  4. for 循环检查每一行。当 line.contains("hello") 为真时,它递增 count 并打印该行(因为 verbose 为真)。
  5. 函数返回 count,main 将其打印为最终摘要。

注意错误处理是多么简洁。read_file 和 process 都返回 !T。每个调用点 的 ! 操作符将错误向上传播。如果 main 想自己处理错误,可以使用 is 表达式代替 !:

auto
is read_file("data.txt") {
    Ok(content) => print(content),
    Err(msg) => eprint("Error: " + msg)
}

这与第 12 章中的相同 Result 模式,现在应用于真实项目场景。

实用文件 I/O 模式 ​

上面的文件处理器演示了带有错误处理的结构化文件处理。以下是你在实践中常见的 四种文件 I/O 模式。

从文件中读取行 ​

最基本的文件操作:写入内容、读回并逐行迭代。

从文件中读取行

统计临时文件中的行数 ​

一种常见的测试模式:创建临时文件,处理后清理。

统计临时文件中的行数

读取 CSV 数据 ​

CSV(逗号分隔值)是最简单的结构化数据格式。在 Auto 中,你可以用 split 按换行符和逗号进行解析。

使用字符串分割读取 CSV 数据

过滤 CSV 数据 ​

将 CSV 解析与条件逻辑结合,可以根据字段值筛选行。

按条件过滤 CSV 行

这四种模式涵盖了最常见的文件 I/O 场景:

  1. 读取行 -- 基础操作:写入、读取、分割、迭代。
  2. 临时文件 -- 创建-处理-删除是标准的测试模式。
  3. CSV 解析 -- 先按换行符分割,再按逗号分割来提取字段。
  4. CSV 过滤 -- 将解析与条件逻辑结合进行数据查询。

总结 ​

这个高潮项目综合了第二阶段的每个概念:

概念在哪里使用
自定义类型Config 类型归组所有设置
字段访问config.input_path、config.verbose
错误处理(!T)read_file 和 process 返回 !T
错误传播每个调用点的 ! 解包
字符串方法s.contains("hello")、content.split("\n")
控制流if/else 用于验证和详细输出
for 循环遍历行
泛型search_lines<T> 带类型参数
高阶函数predicate fn(String) T 参数
匿名函数内联 fn(s String) ?String { ... }
Option 类型(?T)谓词返回 ?String 用于过滤

以下是我们在第二阶段学到的内容:

  1. 类型(第 10 章)给我们带有命名字段的结构化数据。Config 是我们 程序的骨架。

  2. 枚举(第 11 章)建模可以是几种变体之一的数据。?T(Option)和 !T(Result)在底层就是枚举。

  3. 错误处理(第 12 章)使用 !T 让失败显式化。! 解包操作符干净地 传播错误。没有意外崩溃。

  4. 泛型(第 13 章)让我们编写跨类型工作的函数。search_lines<T> 可以 与任何谓词返回类型一起复用。

第三阶段将带来什么:

  • 闭包与迭代器 —— 更强大的集合处理方式
  • 智能指针 —— 所有权引用、内部可变性
  • 并发 —— 编写安全的并行代码
  • 高级类型系统特性 —— 生命周期、trait 对象等

你现在拥有了用 Auto 构建真实程序的工具。文件处理器虽然小巧,但它展示了 与生产代码库相同的模式:结构化类型、显式错误处理和可复用的泛型函数。

基于 MIT 许可发布。