Rust 的变量与类型

Rust 的变量与类型

变量声明

Rust 语言中通过 let 关键字来声明变量。Rust 是一门静态强类型语言,因此任何一个变量都有一个确定、不可变的类型。如果在声明同时初始化,则可以依靠编译器的类型推断来得到变量类型,不一定需要显式指定类型。

let a: i32 = 1; // 完整的变量声明和初始化
let b: i32; // 显式声明类型,未初始化
let c = 1; // 声明同时初始化,类型由编译器推断为 i32
// let d; // 既没有初始化又没有标注类型,编译器无从得知类型,编译无法通过

可变性与不可变性

Rust 语言与许多其他语言不同的一点在于变量的默认不可变性,即变量的值默认是不可修改的。

let a = 1;
// a = 2; // 变量 a 默认不可变,编译无法通过

要声明一个可变的变量,需要加上 mut 关键字。

let mut a = 1;
a = 2; // 变量 a 可变,OK 

常量

Rust 中的常量使用 const 关键字声明,常量的值必须在编译期间确定。常量的类型必须显式指定。

const SECONDS_PER_DAY: i32 = 24 * 60 * 60; // 常量的值必须在编译期间确定. 能够通过编译
// const B = 1; // 常量必须显式指定类型,编译无法通过
const c: i32 = 1; // 能够通过编译,但会被编译器警告,因为常量名的规范是 大写字母+下划线

变量遮蔽

其英文为 shadowing,可被译为“遮蔽”、“重影”,是 Rust 的一种特殊语法,允许在同一作用域中声明一个与之前变量同名的新变量,从而遮蔽之前的变量。

这相当于把变量名绑定到了一个新的值上,而不是修改了原来的值。这种特性在一些场景下很有用,可以减少变量的数量、减轻给变量取名字的负担。

let spaces = "   "; // spaces 是一个字符串
let spaces = spaces.len(); // spaces 变成了一个数字

let mut spaces = "   "; // spaces 是一个字符串
// spaces = spaces.len(); // 编译无法通过,因为 spaces 已经是一个字符串,不能再赋值为数字,类型不匹配

let mut num = 1;
num *= 2; // OK

变量类型

基本类型

整数

Rust 中的整数有许多种,按照长度不同、有无符号进行区分。

Rust的多数整数命名遵循“字母 + 数字”这一结构。其中,以字母 i 开头的类型表示它是有符号整数,而以字母 u 开头的类型表示它是无符号整数,字母后面的数字则表示这一类型的长度,从最短的 8 字节到最长的 128 字节,有 i8, u8, i16, u16, i32, u32, i64, u64, i128, u128 这一系列类型。

编译器将整形字面量默认类型推导为 i32

let a = 1; // a 的类型为(隐式推导为)i32
let b: u32 = 1; // b 的类型(显式声明为)u32

浮点数

类似整形,Rust 中的浮点型以字母 f 开头,后面是对应的长度,但只有 f32f64 两种类型。

编译器将浮点字面量默认类型推导为 f64

let f: f32 = 1.0;
let g = 2.0; // 类型默认推导为 f64

布尔值

Rust 中的布尔型为 bool,有且仅有两个值 truefalse

字符类型

Rust 中一个字符由单引号包裹,表示一个 Unicode 字符而非一个 ASCII 字符或字节

let ascii_char_z = 'z';
let heart_eyed_cat = '😻';

复合类型

元组

元组将几个相同或不同的类型组合为一个复合类型。

let tuple_1: (i32, f64, bool) = (1, 2.0, false); // 显式声明类型
let tuple_2 = (3, 1.0, true); // 隐式推断类型

元组支持通过模式匹配来解构,也可以通过点 . 来访问其元素。

let tuple = (1, 2.0, false);
let (x, y, z) = tuple;
// 上面的 let 语句创建了 i32 类型的变量 x、f64 类型的变量 y 和 bool 类型的变量 z
let xx = tuple.0;
let yy = tuple.1;
let zz = tuple.2;
// xx, yy, zz 分别对应 x, y, z

Rust的数组将固定数目同类型变量储存在一起。

let arr_1: [i32; 5] = [1, 2, 3, 4, 5];
// [i32; 5] 表示包含5个 i32 的数组
let arr_2 = [1.0, 2.0, 3.0, 4.0];
// arr_2 的类型被隐式推断为 [f64; 4]
let arr_long: [i32; 100] = [0; 100];
// arr_long 的类型为 [i32; 100],而且这 100 个元素都是 0

如果需要一个可变长的数组,那么可以使用 Vec

let arr_vec: Vec<i32> = Vec::new();
// Vec::new() 返回一个新的 Vec<i32> 结构体

下面的代码将会在编译时报错:

let arr = [1, 2, 3];
let element = arr[3]; // 编译器报错:index out of bounds: the length is 3 but the index is 3

下面的代码能够通过编译,但当输入的下标超出数组长度时,程序会立即退出而不允许访问越界的内存:

use std::io;

let arr = [1, 2, 3];
let mut index = String::new();
io::stdin().read_line(&mut index).expect("Failed to read line"); // 若输入10...
let index: usize = index.trim().parse().expect("Index must be a number");
let element = arr[index]; // ...程序产生运行时错误并退出
println!("The value of element is: {}", element); // 该行不会被执行

单元类型

Rust 中有一个特殊的单元类型 (Unit Type),亦称 单位元,它的类型是 (),而且它唯一的值也是 ()。例如,当一个表达式或函数什么也不返回时,它的返回类型和返回值就都是 ()

fn some_func() {
    return 0;
}

尝试编译上面的函数,编译器将报告类型不匹配,期望得到 () 而得到了整数。这表明无返回值的函数实质上返回了 ()