1、变量的可变性
变量默认是不可改变的(immutable)。这是 Rust 提供给你的众多优势之一,让你得以充分利用 Rust 提供的安全性和简单并发性来编写代码。不过,你仍然可以使用可变变量。
接下来我们测试一下,使用 cargo new variables命令在 projects 目录生成一个叫做 variables的新项目。
修改main.rs,内容如下所示:
fn main() { let v = 1; v = 2; println!("Hello, world! {v}"); }
在VSCode中打开,我们发现rust-analyzer插件有提示,无法修改不可变变量。
不过可变性也是非常有用的,可以用来更方便地编写代码。尽管变量默认是不可变的,你仍然可以在变量名前添加 mut
来使其可变,代码改成如下所示:
fn main() { let mut v = 1; println!("Hello, world! {v}"); v = 2; println!("Hello, world! {v}"); }
常量
类似于不可变变量,常量 (constants) 是绑定到一个名称的不允许改变的值,不过常量与变量还是有一些区别。
首先,不允许对常量使用 mut
。常量不光默认不可变,它总是不可变。声明常量使用 const
关键字而不是 let
,并且必须声明值的类型。
把上面代码改成如下所示:
fn main() { const PI: f64 = 3.14159; println!("Hello, world! {PI}"); }
隐藏
在同一作用域内,可以多次声明一个变量,可以用相同变量名称来隐藏一个变量,以及重复使用 let
关键字来多次隐藏,如下所示:
fn main() { let name = "zhangsan"; let name = "lisi"; println!("我的名字是:{name}"); { let name = "wangwu"; let name = "zhaoliu"; println!("我的名字是:{name}"); } }
会打印对应的结果,但是重复定义的地方会有提示,说变量未引用,如果是有意为之的,建议加一个下划线的前缀。
隐藏与将变量标记为 mut
是有区别的。当不小心尝试对变量重新赋值时,如果没有使用 let
关键字,就会导致编译时错误。通过使用 let
,我们可以用这个值进行一些计算,不过计算完之后变量仍然是不可变的。
mut
与隐藏的另一个区别是,当再次使用 let
时,实际上创建了一个新变量,我们可以改变值的类型,并且复用这个名字。例如,假设程序请求用户输入空格字符来说明希望在文本之间显示多少个空格,接下来我们想将输入存储成数字(多少个空格):
fn main() { let spaces = "zhangsan"; let spaces = spaces.len(); println!("长度是:{spaces}") }
在编辑中可以看到同一个变量被赋值,对应的类型也改变了。
第一个 spaces
变量是字符串类型,第二个 spaces
变量是数字类型。隐藏使我们不必使用不同的名字,如果我们通过mut声明为可变变量,再看下会怎么样?
fn main() { let mut spaces = "zhangsan"; spaces = spaces.len(); println!("长度是:{spaces}"); }
这说明刚开始的时候,我们声明变量的类型已固定,我们不能改变变量的类型了。
2、数据类型
每一个值都属于某一个 数据类型(data type),这告诉 Rust 它被指定为何种数据,以便明确数据处理方式。我们将看到两类数据类型子集:标量(scalar)和复合(compound)
使用 parse
将 String
转换为数字时,必须增加类型注解,像这样:
let guess:u32 = "100".parse().expect("");
如果没有对guess添加类型定义,会出现以下错误:
2.1 标量类型
标量(scalar)类型代表一个单独的值。Rust 有四种基本的标量类型:整型、浮点型、布尔类型和字符类型。
整型
整数 是一个没有小数部分的数字。前面示例中使用过 u32
整数类型。该类型声明表明,它关联的值应该是一个占据 32 比特位的无符号整数(有符号整数类型以 i
开头而不是 u
),以下表格展示了 Rust 内建的整数类型。
长度 | 有符号 | 无符号 |
8-bit | i8 |
u8 |
16-bit | i16 |
u16 |
32-bit | i32 |
u32 |
64-bit | i64 |
u64 |
128-bit | i128 |
u128 |
arch | isize |
usize |
isize
和 usize
类型依赖运行程序的计算机架构:64 位架构上它们是 64 位的,32 位架构上它们是 32 位的。
注意:可以是多种数字类型的数字字面值允许使用类型后缀,例如
57u8
来指定类型,同时也允许使用_
做为分隔符以方便读数,例如1_000
,它的值与你指定的1000
相同。
数字字面值 | 例子 |
Decimal (十进制) | 98_222 |
Hex (十六进制) | 0xff |
Octal (八进制) | 0o77 |
Binary (二进制) | 0b1111_0000 |
Byte (单字节字符)(仅限于u8 ) |
b'A' |
浮点型
Rust 也有两个原生的 浮点数(floating-point numbers)类型,它们是带小数点的数字。Rust 的浮点数类型是 f32
和 f64
,分别占 32 位和 64 位。默认类型是 f64
,因为在现代 CPU 中,它与 f32
速度几乎一样,不过精度更高。所有的浮点型都是有符号的。
这是一个展示浮点数的实例:
浮点数采用 IEEE-754 标准表示。f32
是单精度浮点数,f64
是双精度浮点数。
数值运算
Rust 中的所有数字类型都支持基本数学运算:加法、减法、乘法、除法和取余。整数除法会向零舍入到最接近的整数。以下是一些示例:
fn main() { let plus = 2 + 3; let sub = 5 - 2; let mul = 2 * 3; let div = 3 /2; let rem = 3 % 2; println!("{plus} - {sub} - {mul} - {div} - {rem}") }
布尔型
Rust 中的布尔类型有两个可能的值:true
和 false
。Rust 中的布尔类型使用 bool
表示。例如:
fn main() { let a = false; let b:bool = true; print!("{a} {b}") }
字符类型
Rust 的 char
类型是语言中最原生的字母类型。下面是一些声明 char
值的例子:
fn main() { let a = 'A'; let b:char = 'a'; println!("{a} - {b}") }
复合类型
复合类型可以将多个值组合成一个类型。Rust 有两个原生的复合类型:元组(tuple)和数组(array)。
元组类型
元组是一个将多个其他类型的值组合进一个复合类型的主要方式。元组长度固定:一旦声明,其长度不会增大或缩小。
我们使用包含在圆括号中的逗号分隔的值列表来创建一个元组。元组中的每一个位置都有一个类型,而且这些不同值的类型也不必是相同的。这个例子中使用了可选的类型注解:
let tup: (char, f32, bool) = ('A', 3.2, true);
tup
变量绑定到整个元组上,因为元组是一个单独的复合元素。为了从元组中获取单个值,可以使用模式匹配(pattern matching)来解构(destructure)元组值,像这样:
fn main() { let tup: (char, f32, bool) = ('A', 3.2, true); let (x,y,z) = tup; println!("{x} {y} {z}") // A 3.2 true }
数组类型
另一个包含多个值的方式是 数组(array)。与元组不同,数组中的每个元素的类型必须相同。Rust 中的数组与一些其他语言中的数组不同,Rust 中的数组长度是固定的。
fn main() { let x = [1, 2, 3, 4, 5]; }
你还可以通过在方括号中指定初始值加分号再加元素个数的方式来创建一个每个元素都为相同值的数组:
fn main() { let a = [3; 5]; }
变量名为 a 的数组将包含 5 个元素,这些元素的值最初都将被设置为 3。这种写法与let a = [3,3,3,3,3]效果一样。
访问数组元素
fn main() { let x = [1, 2, 3, 4, 5]; let y = x[0]; let z = x[9]; println!("{z}") }
我们可以通过数组的索引去取值,跟js,ts语言取值一样。
当访问超过边界的索引时,会报一个运行时的错。
程序在索引操作中使用一个无效的值时导致 运行时 错误。程序带着错误信息退出,并且没有执行最后的 println!
语句。当尝试用索引访问一个元素时,Rust 会检查指定的索引是否小于数组的长度。如果索引超出了数组长度,Rust 会 panic,这是 Rust 术语,它用于程序因为错误而退出的情况。这种检查必须在运行时进行,特别是在这种情况下,因为编译器不可能知道用户在以后运行代码时将输入什么值。