Skip to content

Rust - 输入输出

本章节涵盖了与用户交互的基础知识:从标准输入(键盘)读取和写入标准输出(控制台)。我们还将了解如何处理命令行参数。

Rust 的标准 I/O 库(std::io)围绕两个基本特性(trait)构建:用于输入的 Read 和用于输出的 Write。

  • Read:用于可从中读取字节的源,例如 std::io::stdin() 或 File。
  • Write:用于可向其写入字节的目标,例如 std::io::stdout() 或 File。

要从用户读取一行文本,你可以使用标准输入句柄上的 read_line 方法。此操作可能会失败(例如,由于操作系统级别的错误),因此它返回一个 Result 类型,你必须处理它。

use std::io;
fn main() {
println!("Please enter your name:");
let mut name = String::new();
// 获取标准输入的句柄并读取一行。
// `read_line` 返回一个 `Result`,因此我们使用 `.expect()` 进行简单的错误处理。
io::stdin()
.read_line(&mut name)
.expect("Failed to read line");
// `read_line` 包含换行符,所以我们可以对其进行修剪(trim)。
println!("Hello, {}. Nice to meet you!", name.trim());
}

.expect("message") 调用是一种快捷方式。如果 Result 是 Ok,它会返回内部的值。如果是 Err,它将导致程序 panic(崩溃)并显示你提供的消息。它对于示例和原型很有用,但在生产代码中,你通常应该使用 match 语句或其他方法进行更优雅的错误处理。

你已经看到了最简单的写入控制台的方法:println! 和 print! 宏。它们很方便,并为你处理格式化。

fn main() {
let name = "Alice";
let age = 30;
println!("Name: {}, Age: {}", name, age); // 打印后换行
print!("This will be on the "); // 打印后不换行
print!("same line.\n");
}

在底层,这些宏使用 std::io::stdout() 句柄。如果你需要更多控制,可以直接使用此句柄与 Write trait 配合,但 println! 通常就足够了。

程序通常在命令行中带参数启动。Rust 的标准库提供了通过 std::env::args() 访问这些参数的简单方法。

std::env::args() 返回一个迭代器,它将参数作为字符串产生。第一个参数(args[0])始终是你的程序可执行文件的路径。

// 保存为 main.rs 并运行:`cargo run -- arg1 arg2`
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
println!("All arguments: {:?}", args);
// 第一个参数是程序路径
let program_path = &args[0];
println!("Program path: {}", program_path);
// 访问其他参数
if args.len() > 1 {
let first_arg = &args[1];
println!("First argument: {}", first_arg);
}
}

我们来创建一个程序,它对作为命令行参数传递的数字求和。这涉及到跳过第一个参数,并将其余参数从字符串解析为数字。这是一个迭代器大放异彩的绝佳示例。

// 运行:`cargo run -- 10 20 5`
use std::env;
fn main() {
let mut total = 0;
// env::args() 返回一个迭代器。 .skip(1) 创建一个新的迭代器,
// 跳过第一个元素(程序路径)。
for arg in env::args().skip(1) {
// .parse() 返回一个 `Result`,我们用 `match` 处理它。
match arg.parse::<i32>() {
Ok(num) => {
total += num;
}
Err(_) => {
eprintln!("Error: '{}' is not a valid integer. Skipping.", arg);
}
}
}
println!("Total sum: {}", total);
}

这个例子健壮得多。它使用 .skip(1) 进行地道的迭代,并使用 match 优雅地处理不是有效数字的参数,将错误打印到标准错误(eprintln!)而不是导致程序崩溃。

专业提示:使用 Crate 处理复杂参数

Section titled “专业提示:使用 Crate 处理复杂参数”

对于任何实际应用,手动解析命令行参数可能会变得很繁琐。Rust 生态系统为此提供了优秀的库(称为 ‘crates’)。其中一个非常流行的是 clap。它允许你声明式地定义应用程序的 CLI 接口,并自动为你处理解析、验证和生成帮助消息。

虽然 std::env::args() 对于简单情况非常有用,但一旦你的需求超出少数位置参数,请考虑使用像 clap 这样的 crate。