前言#
昨天我们优化了前天写的猜数游戏小案例,知道了如何使用loop以及枚举(enum)的一些基础用法。
感兴趣的可以看下
经过前两天的这个猜数小案例,也差不多可以适应rust的代码了(吧?)。
今天我们继续跟着文档往下学。
官方文档#
The Rust Programming Languagedoc.rust-lang.org/book/title-page.html
PS: 官方的文档写的挺风趣的
正文#
这一大章挺重要的,是rust的基础,不过学起来也不难,是所有编程语言都有的概念。
创建环境#
我们重开一个project
cd ../
mkdir day3
cd day3
cargo new concept
cd concept 变量与可变性(mutability)#
- 不可变变量
- 可变变量
变量用let关键字声明。
之前的文章中有提到过: **rust中变量默认是不可变的。**看下demo。

不能二次赋值一个不可变的变量。
可变就意味着变量处于不稳定的状态,可能会导致程序出错。
在其它语言中你是否遇到过变量发生改变后导致的程序异常?
在js中应该挺容易遇见的,这种变量值发生变化导致的错误是无法在编译过程中就发现的,也就是说这个是一个runtime bug。要么你开发测试的时候把场景都兼容到,要么就是线上出问题之后猜测代码出错的位置,打断点跟踪(一般工程化打包后,经过uglify等优化操作之后也基本告别打断点了)或者本地复现,简单的还好说,复杂的场景就意味着将耗费大量的时间用于排查,这是一个十分昂贵并且让人痛苦的bug。
所以rust默认变量就是不可变,这样稳定安全。
但是这世间又怎会让人如此舒心,老天答应,你的甲方baba也不答应啊,还是需要可变滴。
看过之前的代码应该知道如何将变量变成可变的,变量名前加个mut即可变成可变变量。
let mut num: i32 = 0;
num = 1;
println!("{num}");常量#
常量,就是字面上(literal)的意思,值是在编译的时候就确定、固定的了。和不可变变量很像,但是还些许不同。我们先看下是如何创建常量的
const STR: &str = "i am a constant variable";相信大家也都猜到了,基于const关键字。
规范是const 变量名大写字母_多个词用下划线分割: 类型必须声明 = 字面上的值,比如123,"test"这样。
在js(ES6+)中,声明一个不可变的量就是用的const关键字,不管是不是常量,都是用const,因为没差,都是runtime。
而在rust中将他俩进行拆分,一方面更方便识别,另一方面const声明的常量会在编译的时候就确定了,而let不可变的却不行,因为它可能被赋值为一个传入的参数,而这个是不可预测的,并不能在编译的时候就确定。
常量右边也可以是一个表达式,但是这个表达式一定是可预测的,比如:

两个const的值相加可以赋值给另一个const。而let则不能用在const赋值中。
赋值表达式尽量写的细一点,比如这个ONE_DAY右边的表达式,一眼就能看出是一天。
const在定义的作用域中会一直有效,而let定义的变量则是不确定的,可能会在某个时间被释放掉,具体分析可以看下day2的。
隐藏(shadowing)#
这个点其实在day2中就已经说过了,不过这里再说点细节。
我们之前说过一个变量是可以被二次声明的,比如
let num: i32 = 0;
let num: String = String::from("test");此时第一个num就被释放掉了。
而在作用域中不会发生shadowing,比如:
fn main() {
let num: i32 = 0;
{
let num: i32 = 1;
println!("inner scope num, {num}");
};
println!("root num, {num}");
}
真是这样吗?还有一种特殊场景我们没有分析
还记得我们在day2中提到shadowing时说的所有权吗?这个是会影响到其他作用域变量的释放问题的(前提是堆内存存储)。
来看下例子
fn main() {
let str: String = String::from("str");
a(str);
println!("main, {str}");
}
fn a (str: String) {
let str: String = str.to_uppercase();
println!("a, {str}");
}
这个报错简单地说就是这个main方法作用域中的str被释放掉了(指的是堆内存上的指针,当然,如果a方法中并没有调用str,堆内存空间也是会被释放掉或者被标记为可覆盖)。
看到这你可能会觉得有些变扭,貌似两个不是同一个问题,确实不是。
不过我是想说明:作用域并不是万能的,利用作用域来绕过shadowing可能会翻车。
以上都是针对变量,也就是用let关键字声明的。
至于常量那当然是不允许被shadowing的(同一级作用域内,如果次级作用域中去“覆盖”它是可以的,完全不影响,但覆盖后的值只在次级作用域中有效)。

fn main() {
const NUM_1: i32 = 1;
a(&NUM_1);
println!("{NUM_1}")
}
fn a (num: &i32) {
let num: i32 = 66;
println!("in a , {num}");
}
最终main作用域中还是1,即使是所有权交由a函数也是不会改变原来的数据。
而shadowing的好处最主要的是节省命名空间以及不用担心因为粗心导致将一个可变的变量进行二次赋值导致数据有问题。
本来想把所有概念都说完的,但是量有些大,还是拆开来算了。
如果觉得对你有用麻烦点个赞,谢谢~
参考#
- ^rust-concept https://doc.rust-lang.org/book/ch03-00-common-programming-concepts.html
- ^rust-variables-and-mutability https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html
- ^rust-constants https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#constants
- ^rust-shadowing https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html#shadowing
发布于 2022-11-28 00:43・IP 属地广东
