简介
Rust 内建数据类型分为两类:标量(scalar)和复合(compound)。
Rust 是 静态类型(statically typed)语言,也就是说在编译时就必须知道所有变量的类型,这一点非常重要,我们将在后续编码中体会到 “ rust 要求明确 ” 的含义。
通过值的形式及其使用方式,编译器通常可以推断出我们想要用的类型。但当有歧义(多种类型均有可能时),必须增加类型注解,像这样:
let guess = "42".parse().expect("Not a number!");
如果不添加类型注解,编译会显示如下错误:
error[E0282]: type annotations needed
--> src/main.rs:2:9
|
2 | let guess = "42".parse().expect("Not a number!");
| ^^^^^
| |
| cannot infer type for `_`
| consider giving `guess` a type
rust 需要我们明确字符转为具体哪种类型的整型,所以我们需要明确类型:
let guess: u32 = "42".parse().expect("Not a number!");
标量类型
标量(scalar)类型代表一个单独的值。
Rust 有四种基本的标量类型:整型、浮点型、布尔类型 和字符类型。
整型
Rust 中的整型类型:
长度 | 有符号 | 无符号 |
---|---|---|
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
arch | isize | usize |
数字字面值
除 byte 外的其它字面值允许使用类型后缀,例如 57u8 ,同时也允许使用 _ 做为分隔符以方便读数,例如 1_000 。
数字字面值 | 例子 |
---|---|
Decimal | 98_222 |
Hex | 0xff |
Octal | 0o77 |
Binary | 0b1111_0000 |
Byte ( u8 only) | b'A' |
那么该使用哪种类型的数字呢?
Rust 的默认类型通常就很好,数字类型默认是 i32 :它通常是最快的,甚至在 64 位系统上也是。 isize 或 usize 主要作为某些集合的索引。
浮点型
Rust 同样有两个主要的 浮点数(floating-point numbers)类型, f32 和 f64 ,它们是带小数点的数字,分别占 32 位和 64 位比特。默认类型是 f64 ,因为在现代 CPU 中它与 f32 速度几乎一样,不过精度更高。
这是一个展示浮点数的实例:
fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}
浮点数采用 IEEE-754 标准表示。 f32 是单精度浮点数, f64 是双精度浮点数。
布尔型
Rust 中的布尔类型有两个可能的值: true 和 false 。Rust 中的布尔类型使用
bool 表示。例如:
fn main() {
let t = true;
let f: bool = false; // with explicit type annotation
}
💡 注意,和 C++ 等语言不同,rust 中的布尔型不能和整型隐式转换。
字符型
Rust 的 char 类型是大部分语言中基本字母字符类型,如下代码展示了如何使用它。注意 char 由单引号指定,不同于字符串使用双引号:
fn main() {
let c = 'z';
let z = 'ℤ';
let heart_eyed_cat = 'ఫ';
}
Rust 的 char 类型代表了一个 Unicode 标量值(Unicode Scalar Value),这意味着它可以比 ASCII 表示更多内容。拼音字母(Accented letters),中文/日文/韩文等象形文字,emoji(絵文字)以及零长度的空白字符对于Rust char 类型都是有效的。Unicode 标量值包含从 U+0000 到 U+D7FF 和 U+E000 到 U+10FFFF 在内的值。不过,“字符” 并不是一个 Unicode 中的概念,所以人直觉上的 “字符” 可能与 Rust 中的 char 并不符合。
复合类型
复合类型(Compound types)可以将多个其他类型的值组合成一个类型。Rust 有两个原生的复合类型:元组(tuple)和 数组(array)。
元组
元组可以包含多种类型的值,它被认为是一个单独的复合元素:
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
tup即为一个元组。
从元组中获取单个的值,可以使用模式匹配(pattern matching)来解构(destructure)元组,像这样:
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {}", y);
}
使用 let 和一个模式将 tup 分成了三个不同的变量, x 、 y 和 z ,这叫做解构(destructuring)。
除了使用模式匹配解构之外,也可以使用点号( . )后跟值的索引来直接访问它们。例如:
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
}
跟大多数编程语言一样,元组的第一个索引值是 0。
数组
数组中的每个元素的类型必须相同。
fn main() {
let a = [1, 2, 3, 4, 5];
}
Rust 中的数组与一些其他语言中的数组不同,因为 Rust 中的数组是固定长度的:一旦声明,它们的长度不能改变!
如果需要增加或删除,则使用标准库提供的 vector 类型.
访问数组元素
数组是一整块分配在栈上的内存。可以使用索引来访问数组的元素,像这样:
fn main() {
let a = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
}
在这个例子中,叫做 first 的变量的值是 1 ,因为它是数组索引 [0] 的值。变量 second 将会是数组索引 [1]的值 2 。
无效的数组元素访问
如果我们访问数组结尾之后的元素会发生什么呢?比如我们将上面的例子改成下面这样,这可以编译。
不过在运行时会因错误而退出:
fn main() {
let a = [1, 2, 3, 4, 5];
let index = 10;
let element = a[index];
println!("The value of element is: {}", element);
}
使用 cargo run 运行代码后会产生如下结果:
$ cargo run
Compiling arrays v0.1.0 (file:///projects/arrays)
Finished dev [unoptimized + debuginfo] target(s) in 0.31 secsRunning `target/debug/arrays`
thread '<main>' panicked at 'index out of bounds: the len is 5 but the index is10', src/main.rs:6
note: Run with `RUST_BACKTRACE=1` for a backtrace.
编译并没有产生任何错误,不过程序会导致一个 运行时(runtime)错误并且不会成功退出。
当尝试用索引访问一个元素时,Rust 会检查指定的索引是否小于数组的长度。如果索引超出了数组长度,Rust 会 panic,这是 Rust 中的术语,它用于程序因为错误而退出的情况。
这是第一个在实战中遇到的 Rust 安全原则的例子。在很多底层语言中,并没有进行这类检查,这样当提供了一个不正确的索引时,就会访问无效的内存。Rust 通过立即退出而不是允许内存访问并继续执行来使你免受这类错误困扰。