Rust处理命令行参数

概述

为了让我们的程序接收一系列数值作为命令行参数并打印出它们的最

大公约数,可以将 src/main.rs 中的 main 函数替换为以下内容:

rust 复制代码
use std::str::FromStr;
use std::env;
fn main() {
	let mut numbers = Vec::new();
	for arg in env::args().skip(1) {
		numbers.push(u64::from_str(&arg).expect("error parsing argument"));
	}
	if numbers.len() == 0 {
		eprintln!("Usage: gcd NUMBER ...");
		std::process::exit(1);
	}
	let mut d = numbers[0];
	for m in &numbers[1..] {
		d = gcd(d, *m);
	}
	println!("The greatest common divisor of {:?} is {}", numbers, d);
}

代码分析

rust 复制代码
use std::str::FromStr;
use std::env;

第一个 use 声明将标准库中的 FromStr 特型引入了当前作用域。

特型是可以由类型实现的方法集合。任何实现了 FromStr 特型的类型都有一个 from_str 方法,该方法会尝试从字符串中解析这个类型的值。u64 类型实现了 FromStr,所以我们将调用u64::from_str 来解析程序中的命令行参数。

尽管我们从未在程序的其他地方用到 FromStr 这个名字,但仍然要 use(使用)它,因为要想使用某个特型的方法,该特型就必须在作用域内。

第二个 use 声明引入了 std::env 模块,该模块提供了与执行环境交互时会用到的几个函数和类型,包括 args 函数,该函数能让我们访问程序中的命令行参数。

代码分析2

rust 复制代码
let mut numbers = Vec::new();

我们声明了一个可变的局部变量 numbers 并将其初始化为空向量。

Vec 是 Rust 的可增长向量类型,类似于 C++ 的 std::vector、Python 的列表或 JavaScript 的数组。虽然从设计上说向量可以动态扩充或收缩,但仍然要标记为 mut,这样 Rust 才能把新值压入末尾。

numbers 的类型是 Vec,这是一个可以容纳 u64 类型的值的向量,但和以前一样,不需要把类型写出来。Rust 会推断它,一部分原因是我们将 u64 类型的值压入了此向量,另一部分原因是我们将此向量的元素传给了 gcd,后者只接受 u64 类型的值。

代码分析3

rust 复制代码
for arg in env::args().skip(1) {

这里使用了 for 循环来处理命令行参数,依次将变量 arg 指向每个参数并运行循环体。

std::env 模块的 args 函数会返回一个迭代器,此迭代器会按需生成1每个参数,并在完成时给出提示。各种迭代器在 Rust 中无处不在,标准库中也包括一些迭代器,这些迭代器可以生成向量的元素、

文件每一行的内容、通信信道上接收到的信息,以及几乎任何有意义的循环变量。Rust 的迭代器非常高效,编译器通常能将它们翻译成与手写循环相同的代码。

除了与 for 循环一起使用,迭代器还包含大量可以直接使用的方法。例如,args 返回的迭代器生成的第一个值永远是正在运行的程序的名称。如果想跳过它,就要调用迭代器的 skip 方法来生成一个新的迭代器,新迭代器会略去第一个值。

代码分析4

rust 复制代码
numbers.push(u64::from_str(&arg).expect("error parsing argument"));

这里我们调用了 u64::from_str 来试图将命令行参数 arg 解析为一个无符号的 64 位整数。u64::from_str 并不是 u64 值上的某个方法,而是与 u64 类型相关联的函数,类似于 C++ 或 Java 中

的静态方法。

from_str 函数不会直接返回 u64,而是返回一个指明本次解析已成功或失败的 Result 值。Result 值是以下两种变体之一:

  • 形如 Ok(v) 的值,表示解析成功了,v 是所生成的值;
  • 形如 Err(e) 的值,表示解析失败了,e 是解释原因的错误
    值。

执行任何可能会失败的操作(例如执行输入或输出或者以其他方式与操作系统交互)的函数都会返回一个 Result 类型,其 Ok 变体会携带成功结果(传输的字节数、打开的文件等),而其 Err 变体会携带错误码,以指明出了什么问题。

与大多数现代语言不同,Rust 没有异常(exception):所有错误都使用 Result 或 panic 进行处理。

Golang也没有异常,作为新设计的语言,这两个语言在对错误处理的理念上非常的相似。

我们用 Result 的 expect 方法来检查本次解析是否成功。如果结果是 Err(e),那么 expect 就会打印出一条包含 e 的消息并直接退出程序。但如果结果是 Ok(v),则 expect 会简单地返回 v 本身,最终我们会将其压入这个数值向量的末尾。

代码分析5

rust 复制代码
if numbers.len() == 0 {
	eprintln!("Usage: gcd NUMBER ...");
	std::process::exit(1);
}

空数组没有最大公约数,因此要检查此向量是否至少包含一个元素,如果没有则退出程序并报错。这里我们用 eprintln! 宏将错误消息写入标准错误流。

这里eprintln的e表示的是stderr,也就是标准错误输出流。

代码分析6

rust 复制代码
let mut d = numbers[0];
for m in &numbers[1..] {
	d = gcd(d, *m);
}

该循环使用 d 作为其运行期间的值,不断地把它更新为已处理的所有数值的最大公约数。和以前一样,必须将 d 标记为可变,以便在循环中给它赋值。

这个 for 循环有两个值得注意的地方。首先,我们写了 for m in &numbers1...,那么这里的 & 运算符有什么用呢?其次,我们写了 gcd(d, *m),那么 *m 中的 * 又有什么用呢?这两个细节是紧密相关的。

迄今为止,我们的代码只是在对简单的值(例如适合固定大小内存块的整数)进行操作。但现在我们要迭代一个向量,它可以是任意大小,而且可能会非常大。Rust 在处理这类值时非常慎重:它想让程序员控制内存消耗,明确每个值的生存时间,同时还要确保当不再需要这些值时能及时释放内存。

所以在进行迭代时,需要告诉 Rust,该向量的所有权应该留在numbers 上,我们只是为了本次循环而借用它的元素。

&numbers1... 中的 & 运算符会从向量中借用从第二个元素开始的引用。for 循环会遍历这些被引用的元素,让 m 依次借出每个元素。*m 中的 * 运算符会将 m解引用,产生它所引用的值,这就是要传给 gcd 的下一个 u64。最后,由于 numbers 拥有着此向量,因此当 main 末尾的 numbers 超出作用域时,Rust 会自动释放它。

Rust 的所有权规则和引用规则是 Rust 内存管理和并发安全的关键所在。只有熟悉了这些规则,才算熟练掌握了 Rust。但是对于这个介绍性的导览,你只需要知道&x 借用了对 x 的引用,而 *r 访问的是 r 所引用的值就足够了。

代码分析7

rust 复制代码
println!("The greatest common divisor of {:?} is {}", numbers, d);

遍历 numbers 的元素后,程序会将结果打印到标准输出流。

println! 宏会接受一个模板字符串,在模板字符串中以 {...} 形式标出的位置按要求格式化并插入剩余的参数,最后将结果写入标准输出流。

C 和 C++ 要求 main 在程序成功完成时返回 0,在出现问题时返回非零的退出状态,而 Rust 假设只要 main 完全返回,程序就算成功完成。只有显式地调用像 expect 或 std::process::exit 这样的函数,才能让程序以表示错误的状态码终止。

运行程序

cargo run 命令可以将参数传给程序,因此可以试试下面这些命令行处理:

bash 复制代码
cargo run 42 56
cargo run 83
cargo run

查看文档

本节使用了 Rust 标准库中的一些特性。如果你好奇还有哪些别的特性,强烈建议看看 Rust 的在线文档。它具有实时搜索功能,能让你的探索更容易,其中还包括指向源代码的链接。

安装 Rust 时,rustup 命令会自动在你的计算机上安装一份文档副本。

你既可以在Rust 网站上查看标准库文档,也可以使用以下命令打开浏览器查看。

bash 复制代码
rustup doc --std

实战案例1:Vec向量的基本用法

创建项目:

bash 复制代码
cargo new hello

编写代码:

复制代码
cd hello
vim src/main.rs

完整代码:

rust 复制代码
fn main() {
    let mut numbers = Vec::new();

    numbers.push(11);
    numbers.push(22);
    numbers.push(33);

    println!("numbers = {:?}", numbers);
}

运行:

bash 复制代码
zhangdapeng@zhangdapeng:~/code/hello$ cargo run
   Compiling c10_func v0.1.0 (/home/zhangdapeng/code/hello)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.91s
     Running `target/debug/c10_func`
numbers = [11, 22, 33]

清理:

bash 复制代码
zhangdapeng@zhangdapeng:~/code/hello$ cargo clean
     Removed 36 files, 8.3MiB total

实战案例2:获取命令行参数

创建项目:

bash 复制代码
cargo new hello

编写代码:

复制代码
cd hello
vim src/main.rs

完整代码:

rust 复制代码
use std::str::FromStr;
use std::env;

fn main() {
    println!("args = {:?}", env::args());
    
    for arg in env::args().skip(1){
        println!("arg = {}", arg);
        println!("arg = {}", u64::from_str(&arg).expect("error parsing argument"));
    }
}

运行:

bash 复制代码
zhangdapeng@zhangdapeng:~/code/hello$ cargo run 11 22
   Compiling c10_func v0.1.0 (/home/zhangdapeng/code/hello)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.23s
     Running `target/debug/c10_func 11 22`
args = Args { inner: ["target/debug/c10_func", "11", "22"] }
arg = 11
arg = 11
arg = 22
arg = 22

清理:

bash 复制代码
zhangdapeng@zhangdapeng:~/code/hello$ cargo clean
     Removed 43 files, 8.4MiB total

实战案例3:求命令行参数的最大公约数

创建项目:

bash 复制代码
cargo new hello

编写代码:

复制代码
cd hello
vim src/main.rs

完整代码:

rust 复制代码
use std::str::FromStr;
use std::env;

fn gcd(mut m: u64, mut n:u64) -> u64 {
    assert!(m != 0 && n != 0);
    while n != 0 {
         if n < m {
            let t = n;
            n = m;
            m = t;
         }
         n %= m;
    }
    m
}

#[test]
fn test_gcd(){
    assert_eq!(gcd(14, 15), 1);
    assert_eq!(gcd(2*3*5*11*17, 3*7*11*13*19), 3*11);
}

fn main() {
    let mut numbers = Vec::new();
    for arg in env::args().skip(1){
        numbers.push(u64::from_str(&arg).expect("error parsing argument"));
    }

    if numbers.len() == 0{
        eprintln!("Usage: gcd NUMBER ...");
        std::process::exit(1);
    }

    let mut d = numbers[0];
    for m in &numbers[1..]{
        d = gcd(d, *m);
    }
    println!("The greatest common divisor of {:?} is {}", numbers, d);
}

测试:

bash 复制代码
zhangdapeng@zhangdapeng:~/code/hello$ cargo test
   Compiling c10_func v0.1.0 (/home/zhangdapeng/code/hello)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.41s
     Running unittests src/main.rs (target/debug/deps/c10_func-7066c0fd0fc42bb9)

running 1 test
test test_gcd ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

运行:

bash 复制代码
zhangdapeng@zhangdapeng:~/code/hello$ cargo run 33 99
   Compiling c10_func v0.1.0 (/home/zhangdapeng/code/hello)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.21s
     Running `target/debug/c10_func 33 99`
The greatest common divisor of [33, 99] is 33

运行2:

bash 复制代码
zhangdapeng@zhangdapeng:~/code/hello$ cargo run 33
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/c10_func 33`
The greatest common divisor of [33] is 33

运行3:

bash 复制代码
zhangdapeng@zhangdapeng:~/code/hello$ cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/c10_func`
Usage: gcd NUMBER ...

清理:

bash 复制代码
zhangdapeng@zhangdapeng:~/code/hello$ cargo clean
     Removed 83 files, 15.6MiB total
相关推荐
爱勇宝40 分钟前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员
AskHarries1 小时前
工具失败时怎么办:重试、回滚、人工确认和风险提示
后端·程序员
苏三说技术3 小时前
Claude Code从失控到起飞,只用了这些技巧
后端
长栎3 小时前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode3 小时前
Redis 在生产项目的使用
前端·后端
用户559822481224 小时前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode4 小时前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战4 小时前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha4 小时前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn4 小时前
Docker 容器管理入门 — 从镜像到容器编排
后端