Rust的变量必须先声明后使用。对于局部变量,最常见的声明语法为:
let variable:i32 =100;
与传统的C/C++语言相比,Rust的变量声明语法不同。这样设计主要有以下几个方面的考虑。
语法分析更容易
从语法分析的角度来说,Rust的变量声明语法比C/C++语言的简单,局部变量声明一定是以关键字let开头,类型一定是跟在冒号:的后面。语法歧义更少,语法分析器更容易编写。
方便引入类型推导功能
Rust的变量声明的一个重要特点是:要声明的变量前置,对它的类型描述后置。这也是吸取了其他语言的教训后的结果。
因为在变量声明语句中,最重要的是变量本身,而类型其实是个附属的额外描述,并非必不可少的部分。
如果我们可以通过上下文环境由编译器自动分析出这个变量的类型,那么这个类型描述完全可以省略不写。
Rust一开始的设计就考虑了类型自动推导功能,因此类型后置的语法更合适。
模式解构
let语句不光是局部变量声明语句,而且具有pattern destructure(模式解构)的功能。
实际上,包括C++/C#/Java等传统编程语言都开始逐步引入这种声明语法,目的是相似的。
Rust中声明变量缺省是“只读”的,比如如下程序:
如果我们需要让变量是可写的,那么需要使用mut关键字:
let语句在此处引入了一个模式解构,我们不能把let mut视为一个组合,而应该将mut x视为一个组合。
mut x是一个“模式”,我们还可以用这种方式同时声明多个变量:
Rust中,每个变量必须被合理初始化之后才能被使用。使用未初始化变量这样的错误,在Rust中是不可能出现的(利用unsafe做hack除外)。
Rust里面的下划线是一个特殊的标识符,在编译器内部它是被特殊处理的。它跟其他标识符有许多重要区别。比如,以下代码就编译不过:
Rust允许在同一个代码块中声明同样名字的变量。如果这样做,后面声明的变量会将前面声明的变量“遮蔽”(Shadowing)起来。
从第5行开始,一直到这个代码块结束,我们没有任何办法再去访问前一个x变量,因为它的名字已经被遮蔽了。
变量遮蔽在某些情况下非常有用,比如,我们需要在同一个函数内部把一个变量转换为另一个类型的变量,但又不想给它们起不同的名字。
再比如,在同一个函数内部,需要修改一个变量绑定的可变性。
例如,我们对一个可变数组执行初始化,希望此时它是可读写的,但是初始化完成后,我们希望它是只读的。
反过来,如果一个变量是不可变的,我们也可以通过变量遮蔽创建一个新的、可变的同名变量。
Rust的类型推导功能是比较强大的。它不仅可以从变量声明的当前语句中获取信息进行推导,而且还能通过上下文信息进行推导。
我们甚至还可以只写一部分类型,剩下的部分让编译器去推导,比如下面的这个程序,我们只知道players变量是Vec动态数组类型,但是里面包含什么元素类型并不清楚,可以在尖括号中用下划线来代替:
Rust依然是静态类型的。
一个变量的类型必须在编译阶段确定,且无法更改,只是某些时候不需要在源码中显式写出来而已。
这只是编译器给我们提供的一个辅助工具。
Rust只允许“局部变量/全局变量”实现类型推导,而函数签名等场景下是不允许的,这是故意这样设计的。
这是因为局部变量只有局部的影响,全局变量必须当场初始化而函数签名具有全局性影响。
函数签名如果使用自动类型推导,可能导致某个调用的地方使用方式发生变化,它的参数、返回值类型就发生了变化,进而导致远处另一个地方的编译错误,这是设计者不希望看到的情况。
我们可以用type关键字给同一个类型起个别名(type alias)。示例如下:
类型别名还可以用在泛型场景,比如:
type Double=(T,Vec);//小括号包围的是一个tuple,请参见后文中的复合数据类型那么以后使用Double的时候,就等同于(i32,Vec),可以简化代码。
Rust中可以用static关键字声明静态变量。如下所示:
static GLOBAL:i32 =0;
与let语句一样,static语句同样也是一个模式匹配。与let语句不同的是,用static声明的变量的生命周期是整个程序,从启动到退出。
static变量的生命周期永远是’static,它占用的内存空间也不会在执行过程中回收。这也是Rust中唯一的声明全局变量的方法。
由于Rust非常注重内存安全,因此全局变量的使用有许多限制。这些限制都是为了防止程序员写出不安全的代码:
在Rust中还可以用const关键字做声明。如下所示:
const GLOBAL: i32 =0;
编译器并不一定会给const常量分配内存空间,在编译过程中,它很可能会被内联优化。
因此,用户千万不要用hack的方式,通过unsafe代码去修改常量的值,这么做是没有意义的。
以const声明一个常量,也不具备类似let语句的模式匹配功能。