2075 字
约 6 分钟
1
变量

变量

在 Rust 中, 变量声明使用的是 let 关键字。

fn main() {
  let msg: &str = "Hello, world!";
  println!("{}", msg);
}

我们不需要每个变量都专门标注类型, 编译器会自动推断出变量的类型。 只有当编译器无法推断出变量类型时, 才需要手动标注类型。

因此以上代码可以更改为:

fn main() {
  let msg = "Hello, world!";
  println!("{}", msg);
}

我们的 CLI 用于记录 Todo 事项, 因此我们需要可以输入内容。

Rust 官方提供了标准库 std::env 用来获取环境信息。它提供了一个 args 函数, 允许获取命令行参数。

更改 main.rs 为如下内容:

fn main() {
  // 因为 args 函数返回的是一个迭代器, 我们需要将其收集为一个数据集合
  // 用户输入是未知的, 因此我们需要指定收集的输入类型, 即 String
  let args: Vec<String> = std::env::args().collect();

  println!("{:#?}", args);
}

命令行运行 cargo run -- a b, 结果如下:

[
  "target\\debug\\cli.exe",
  "a",
  "b",
]

可以看见, 我们获得的输入是一个数组格式, 它的第一项是我们的可执行文件路径。

我们需要的是输入的内容, 即 ab

fn main() {
  let args: Vec<String> = std::env::args().collect();

  let title = args[1];
  let content = args[2];

  println!("todo title: {}, content: {}", title, content);
}

运行 cargo run -- a b。会发现有报错:

error[E0507]: cannot move out of index of `Vec<String>`
 --> src\main.rs:6:15
  |
6 |   let title = args[1];
  |               ^^^^^^^ move occurs because value has type `String`, which does not implement the `Copy` trait
  |
help: consider borrowing here
  |
6 |   let title = &args[1];
  |               +
help: consider cloning the value if the performance cost is acceptable
  |
6 |   let title = args[1].clone();
  |                      ++++++++

error[E0507]: cannot move out of index of `Vec<String>`
 --> src\main.rs:7:17
  |
7 |   let content = args[2];
  |                 ^^^^^^^ move occurs because value has type `String`, which does not implement the `Copy` trait
  |
help: consider borrowing here
  |
7 |   let content = &args[2];
  |                 +
help: consider cloning the value if the performance cost is acceptable
  |
7 |   let content = args[2].clone();
  |                        ++++++++

所有权

以上报错的关键如下:

cannot move out of index of `Vec<String>`

move occurs because value has type `String`, which does not implement the `Copy` trait

它的意思是: 无法从 Vec<String> 中取出值, 因为 String 类型没有实现 Copy 特征, 无法被隐式复制。

在前面有提及到:

Rust 它从语言设计层面 “不信任开发者”, 认为 “你总有一天会犯错”。

因此, Rust 引入了所有权系统、借用检查和生命周期机制。以求在编译阶段就将那些 “未来可能出问题的代码” 拒之门外。

这里就是因为 Rust 引入的所有权系统导致的问题。

根据 Rust 所有权规则:

  • 每个值都有一个所有者。
  • 每个值同时只能有一个所有者。
  • 当所有者离开作用域时, 这个值将被丢弃。

以上报错就很好理解了。

我们试图从 Vec<String> 这个类型中取出值, 但是根据所有权原则, 每个值都只能有一个所有者。 因此 Vec<String> 拥有它内部所有 String 元素的所有权。

当我们使用 args[1] 这样的方式访问时, 实际上是尝试将该元素的所有权 “移动” 到另一个变量。这就违反了所有权规则, 因为 args 还可能在后续被使用, 如果移动了元素所有权, 那么会导致它内部状态不一致, 甚至出现悬垂指针、重复释放等问题。

引用和借用

Rust 在其语言设计层面上“不信任开发者”, 因此它采用了所有权系统来强制保障内存安全。 也因此编译器非常“智能”, 它不仅会告诉你哪里出错了, 还会提供修复建议。

比如, 下面的编译错误信息中就给出了两种可能的解决方式:

help: consider borrowing here
  |
6 |   let title = &args[1];
  |               +
help: consider cloning the value if the performance cost is acceptable
  |
6 |   let title = args[1].clone();
  |                      ++++++++

第一种方法是 let title = &args[1];, 它表示借用 args[1] 的值, 而不移动它的所有权。 这种方式高效, 不会复制数据, 但是变量的类型将变为 &String, 表示这个变量是一个 String 值的引用。 因此它将受到引用对象的限制。当 args 失效, 那么它的引用也将失效。

而第二种方法是 let title = args[1].clone();, 它表示克隆 args[1] 的值, 并将这个值移动到 title 变量中, 这样 args 失效时, 也不会影响 title 的使用。

因此, 我们选择使用第二种方式, 显式调用 clone 方法, 克隆一份 args[1] 的值。

创建一个引用的行为叫做借用。引用则是借用这个行为的结果。

我们再次运行 cargo run -- a b, 可以发现编译通过了。

可变变量

在当前实现中, 每次运行程序都需要输入两个参数(标题和内容), 否则程序会因索引越界而报错。 为了提升程序的健壮性, 我们可以为缺失的参数设置默认值。

修改代码:

fn main() {
  let args: Vec<String> = std::env::args().collect();
  let len = args.len();
  let title = args[1].clone();
  let content = String::from("default content");

  if len > 2{
    content = args[2].clone();
  }

  println!("todo title: {}, content: {}", title, content);
}

以上代码中, 我们对输入参数做了检查, 一旦参数数量大于 2 个, 就会使用第三个参数作为内容。否则就会使用默认值。

执行 cargo run -- a, 发现又有报错了。

error[E0384]: cannot assign twice to immutable variable `content`
  --> src\main.rs:10:5
   |
7  |   let content = String::from("default content");
   |       ------- first assignment to `content`
...
10 |     content = args[2].clone();
   |     ^^^^^^^ cannot assign twice to immutable variable
   |
help: consider making this binding mutable
   |
7  |   let mut content = String::from("default content");
   |       +++

这是因为 Rust 出于安全性和可读性考虑, 默认所有变量都是不可变的。 这段报错的意思是: 不能对不可变变量 content 进行二次赋值, 除非将它声明为可变的。

编译器已经为我们提示了。在 let 后面增加 mut 关键字即可。

fn main() {
  let args: Vec<String> = std::env::args().collect();
  let len = args.len();
  let title = args[1].clone();
  let mut content = String::from("default content");

  if len > 2{
    content = args[2].clone();
  }

  println!("todo title: {}, content: {}", title, content);
}

再次执行 cargo run -- a, 成功运行。

变量类型

Rust 是一门强类型的语言, 这意味着变量在编译时必须要有明确的类型。

类型确定方式有两种, 分别是显式声明和隐式推断。

显式声明, 在变量名称后面使用 : 指定类型。 例如: let args: Vec<String> = std::env::args().collect();。将变量 args 的类型指定为 Vec<String>

隐式推断, 编译器根据变量的值和上下文推断变量的类型。 而 Rust 有着强大的类型推断机制, 使得我们在大多数情况下, 不需要手动标注类型。 编译器会自动推断类型, 只有当编译器无法推断类型时才需要手动标注。

例如以下代码中, 我们并未显式声明 len, titlecontent 的类型, 但它们的类型仍然是确定的:

  let args: Vec<String> = std::env::args().collect();
  let len /* usize */ = args.len();
  let title /* String */ = args[1].clone();
  let mut content /* String */ = String::from("default content");

Rust 支持常见的基本类型:

  • 整型: i8, i16, i32, i64, i128, isize
  • 无符号整型: u8, u16, u32, u64, u128, usize
  • 浮点数: f32, f64
  • 布尔值: bool
  • 字符: char

需要注意的是, Rust 中, "xxx" 是一个字符串字面量切片, 类型为 &str, 是在编译时就固定不可变的。 而 String 是一个字符串类型, 编译时动态分配, 可变长度。

我们在前面使用的 argsVec<String>, 就是一个动态字符串的集合。

变量
http://clxhxhhr.top/posts/441/
作者
clxstart
发布于
2026-09-04
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。
文章目录
目录