变量绑定
在rust里有个核心原则,那就是所有权。在其它语言中,我们可以把一个值赋值给变量。但是在rust里,是把值绑定到变量上。任何内存对象都是有主人的,而且一般情况下完全属于它的主人,绑定就是把这块内存绑定给一个变量,让这个变量成为它的主人。
不可变变量
在rust里,默认情况下变量是不可变的。一旦变量被声明为不可变,那么一旦为它绑定值,就不能再修改。变量的声明方式如下所示:
代码语言:javascript复制let variable_name:type = value
let是rust声明变量的关键字。下面是一个例子:
代码语言:javascript复制fn main() {
let num = 123;
println!("{}", num); // {}是占位符,类似与C/C 中的%d, %s等
}
示例中声明了一个不可变变量num,绑定的值是123。如果尝试对num进行修改,会报错。例如:
代码语言:javascript复制fn main() {
let num = 123;
println!("{}", num);
num = 456; // 错误,不能对不可变变量进行二次赋值。
println!("{}", num);
}
使用cargo check来进行语法检查,报错如下所示:
类型声明
如果你仔细观察你的vscode编辑器,就会发现如下所示的细节。
i32是rust的基本数据类型,这里是rust编译器帮我们进行了类型推断,将num设置为i32类型。大多数情况下,rust编译器都可以帮助我们进行类型的推断,但是偶尔也无法推断,这时候我们就要手动声明变量的数据类型了。例如:
rust编译器帮助我们推断的类型是i32,但是你使用cargo check检查的时候,就会发现有语法错误。rust编译器非常智能的提示你,i32的范围是-2147483648到2147483647,而111111111111不在这个范围内,并且提示你可以使用i64类型替代i32类型。让我们将i32改为i64,再执行cargo check。
可以看到,成功通过了语法检查。在现在的版本(rust1.65),可以通过下面的方式来获取数据类型的名称。
代码语言:javascript复制fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>())
}
例如:
代码语言:javascript复制fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>())
}
fn main() {
let s = "Hello";
let i = 42;
print_type_of(&s); // &str
print_type_of(&i); // i32
print_type_of(&main); // playground::main
print_type_of(&print_type_of::<i32>); // playground::print_type_of<i32>
print_type_of(&{ || "Hi!" }); // playground::main::{{closure}}
}
官方文档显示该函数是不稳定版本的,后续可能会发生变化。
可变变量
由于rust定义的变量默认情况下是不可变变量。可以通过 mut 关键字让变量成为可变变量。例如:
代码语言:javascript复制fn main() {
let mut a = 123;
println!("{}", a);
a = 456;
println!("{}", a);
}
使用mut修饰之后的变量a,可以在后续的过程中进行重新绑定。这段代码执行的结果是:
代码语言:javascript复制123
456
变量默认是不可变的这一点,是 Rust 提供给你的众多优势之一,让你得以充分利用 Rust 提供的安全性和简单并发性来编写代码。
使用下划线开头忽略未使用的变量
如果你创建了一个变量却不在任何地方使用它,Rust 通常会给你一个警告(不是error,这点和go语言不一样),因为这可能会是个 BUG。如果你需要暂时忽略掉这个警告,可以让变量以下划线开头,例如:
代码语言:javascript复制fn main() {
let a = 1;
let b = 2;
println!("{}", a);
}
使用cargo run执行这段代码,输出如下所示:
rust这个编译器是真的非常智能,它警告你,未使用变量b并且给出了解决方案,在b前面加上前缀_即可。让我们加上前缀在来试试。
rust对于未使用的变量只是给出警告,而不像go语言那样未被使用的变量是会报错的。只是个警告这一点非常方便我们进行调试,如果在go语言中,就不太方便调试。
变量遮蔽(shadowing)
Rust 允许声明相同的变量名,在后面声明的变量会遮蔽掉前面声明的。例如:
代码语言:javascript复制use::std::io;
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>())
}
fn main() {
println!("Please input a number:"); // 输出提示
let mut num = String::new(); // 定义一个名为num的String类型的可变变量
io::stdin().read_line(&mut num).expect("Failed to read line"); // 从标准输入读取一行内容,保存到num中
print_type_of(&num); // 打印数据类型
let num: i32 = match num.trim().parse() { // 将String类型的num转为i32类型的num,注意这里使用let再次声明num,并让num绑定一个i32数据
Ok(num) => num,
Err(_) => {
println!("请输入整数,请勿输入无关符号!");
return;
}
};
print_type_of(&num); // 打印数据类型
println!("number is {num}"); // 输出数字。
}
执行上面这段代码,打印结果如下所示:
可以看到,前后两次num的数据类型发生了变化。这段代码先将一个空字符串(String::new())绑定到变量num上,因此我们第一次打印变量num的数据类型是“alloc::string::String”,接下来,代码从标准输入读取一行内容存放到num中(修改了原来的空字符串)。接着将字符串num转为i32值然后重新绑定到num上,这就导致num的数据类型变为了i32。
像上面的场景,从标准输入读取的数字是字符串类型,但是我希望它是i32类型。变量覆盖避免了像num_str这样的名称,你可以重新绑定到新的值上,而不必去给变量起一个复杂的名称;也避免了大量含义不明的tmp变量或者是xxx_tmp变量这样的名称。
变量遮蔽会涉及一次内存对象的在分配,而不像mut变量那样,它是在原来的内存上做修改。
常量
变量的值不能更改可能让你想起其他另一个很多语言都有的编程概念:常量(constant)。与不可变变量一样,常量也是绑定到一个常量名且不允许更改的值,但是常量和变量之间存在一些差异:
- 常量不允许使用 mut。常量不仅仅默认不可变,而且自始至终不可变,因为常量在编译完成后,已经确定它的值。(因此,rust里的常量和C/C 中的宏非常类似,它要求在编译期是确定的,而不可变变量的值可以在运行时确定。)
- 常量使用 const 关键字而不是 let 关键字来声明,并且值的类型必须标注。
Rust 常量的命名约定是全部字母都使用大写,并使用下划线分隔单词,另外对数字字面量可插入下划线以提高可读性,如果未遵守命名规范,rust会给出警告信息。如下所示:
警告信息告诉我们应该将常量max_num变为大写的名称,并且在给出了帮助信息,提示我们将max_num改为MAX_NUM。rust编译器真的非常友好。
参考资料
- rust圣经
- rust程序设计语言