任何一门编程语言几乎都脱离不了:变量、基本类型、函数、注释、循环、条件判断,这是一门编程语言的语法基础,只有当掌握这些基础语法及概念才能更好的学习 Rust。
在 Rust 中,标量类型代表单个值,Rust 有四种基本的标量类型:整型、浮点型、布尔类型和字符类型。
整数是没有小数部分的数字,可以是正数、负数或零。整数类型分为两大类:有符号和无符号。有符号整数可以存储包括负数在内的值,而无符号整数只能存储零和正数。
fn main() {
let x: i32 = -123; // 有符号整数
let y: u32 = 456; // 无符号整数
}
长度 | 有符号整数 | 无符号整数 |
---|---|---|
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
两种整数类型的位长度取决于所运行的平台,如果是 32 位架构的处理器将使用 32 位长度整型 (即 i32 / u32
),64 位架构的处理器上使用 64 为长度整型 (即 i64 / u64
)。
整数类型具有明确的大小(取值范围),每个有符号整数类型可以存储 − 2 n − 1 -2^{n-1} −2n−1 到 2 n − 1 − 1 2^{n-1} - 1 2n−1−1,其中 n
为数据类型的位数,每个无符号整数类型可以存储 0 到 2 n − 1 2^{n} - 1 2n−1。
举个:
i8
可以存储 − 2 8 − 1 -2^{8-1} −28−1 到 2 8 − 1 − 1 2^{8-1} - 1 28−1−1 的数字,即: -128 到 127。u8
可以存储 0 到 2 8 − 1 2^{8} - 1 28−1 的数字,即: 0 到 255。整数字面量可以通过不同的进制来表示,包括十进制、十六进制、八进制、二进制,以及字节(只适用于 u8
)。
进制 | 前缀 | 示例 |
---|---|---|
十进制 | 无 |
98_222 |
十六进制 | 0x |
0xff |
八进制 | 0o |
0o77 |
二进制 | 0b |
0b111_0000 |
字节(u8 专用) | b |
b'A' |
注意:在 Rust 中,数字可以使用 _
作为可视分隔符来提高可读性,编译器在处理数值时会忽略这些 _
。
浮点数是用来处理带有小数部分的数值。Rust 提供了两种基本的浮点类型,分别是 f32
和 f64
,其中 f32
是单精度浮点类型,f64
是双精度浮点类型。Rust 中的浮点类型遵循 IEEE-754 标准。
fn main() {
let x: f32 = 3.14; // f32 单精度浮点数
let y: f64 = 2.71828; // f64 双精度浮点数
let z = 2.71828; // 默认是 f64 双精度浮点数
}
f32
类型的浮点数是单精度浮点数,占用 32 位(4 字节)的内存空间。它的范围大约是 1.4E-45 到 3.4E+38,精度大约有 6-7 位十进制数。f32
类型的浮点数对于需要较高性能但不需要非常高精度的场景来说是一个不错的选择,因为它在处理速度和内存使用上都比 f64
更加高效。
f64
类型的浮点数是双精度浮点数,占用 64 位(8 字节)的内存空间。它的范围大约是 4.9E-324 到 1.8E+308,精度大约有 15-16 位十进制数。相比 f32
,f64
提供了更大的数值范围和更高的精度,适合对数值精度要求较高的计算任务。由于在现代 CPU 架构中,f64
的性能通常也非常高效,所以 Rust 默认的浮点类型就是 f64
。
浮点数可以使用字面量表示法来表示,例如:
3.14
2.5e10
或 2.5E10
表示 2.5 × 10¹⁰
1_000.75_001
+
)、减 (-
)、乘 (*
)、除 (/
)、求余 (%
) 等算术运算能力。f32
或 f64
类型的数值。这类舍入错误是所有使用 IEEE-754 标准的浮点数表示法的编程语言的通病。布尔(Boolean)类型用 bool
表示,它是最简单的类型,只有两个值:true
和 false
。布尔类型通常用于执行逻辑操作,条件判断和控制流程(例如,if
条件语句和循环控制)
fn main() {
let t: bool = true;
let f: bool = false;
}
bool
。true
或 false
。1 byte
或 8 bits
)来存储true
表示逻辑真。false
表示逻辑假。布尔类型支持多种逻辑操作,包括但不限于:
&&
): 当两个操作数都为 true
时,返回 true
;否则返回 false
。||
): 只要一个操作数为 true
,就返回 true
;如果两个都为 false
,则返回 false
。!
): 如果操作数为 true
,返回 false
;如果为 false
,返回 true
。let a = true;
let b = false;
let and_result = a && b; // 返回 false
let or_result = a || b; // 返回 true
let not_result = !a; // 返回 false
字符类型(char
)用于表示单个 Unicode 标量值,这意味着它可以表示比 ASCII 更广泛的字符集。在 Rust 中,字符(char
)是通过单引号('
)来表示的,而字符串(String
或 &str
)是通过双引号("
)来表示的。
fn main() {
let c: char = 'z';
let z: char = 'ℤ'; // Unicode值
let heart_eyed_cat = '';
}
char
。char
类型在 Rust 中是四个字节的大小,即 32 位(与 UTF-32 编码相同)。这是因为 char
需要能表示任意一个 Unicode 标量值,其范围从 U+0000
到 U+D7FF
和 U+E000
到 U+10FFFF
。char
类型实例,并且占用 4 个字节的存储空间。这与其他一些语言中的字符类型(例如 C/C++ 中的 char
)不同,其通常是基于 ASCII 并且仅占用 1 个字节。'a'
, 'Z'
, '7'
等。'\n'
,制表符 '\t'
,单引号 '\''
,反斜杠 '\\'
等。\u{}
转义和大括号内的十六进制数值来表示,如 '好'
可以用 '\u{597D}'
表示。char
类型的值可以直接参与比较操作(==, !=
, <
, >
, 等)。char
类型拥有多种方法用于检查字符的属性(例如 is_alphabetic
, is_numeric
等)。let c1 = 'A';
let c2 = '\u{597D}'; // 表示 "好"
if c1.is_alphabetic() {
println!("{} 是字母", c1);
}
if c2.is_numeric() {
println!("{} 是数字", c2);
}
复合类型可以将多个值组合成一个类型。Rust 主要有两种复合类型:元组(Tuple)和数组(Array)。
元组是可以包含多个不同类型值的一种集合。元组的长度固定,一旦声明,它的长度不会改变。
let tup: (i32, f64, char) = (500, 6.4, 'y');
在这个例子中,tup
是一个元组,包含了一个 i32
类型的整数、一个 f64
类型的浮点数,以及一个 char
类型的字符。
元组可以被解构(destructured),为其内部的每个值匹配一个变量名称。
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {}", y); // 输出 6.4
也可以使用索引访问元组中的元素。
let five_hundred = tup.0;
let six_point_four = tup.1;
let y = tup.2;
数组是有多个相同类型值组成的集合。与元组一样,数组的长度也是固定的,Rust 的数组一旦声明,它的长度不能改变。
let a = [1, 2, 3, 4, 5];
这里,a
是一个整型数组,包含五个整数。
你也可以在声明数组时添加类型注解,指明数组中的元素类型以及数组的长度。
let a: [i32; 5] = [1, 2, 3, 4, 5];
如果数组中的每个元素都是相同的值,可以使用以下方式进行初始化。
let a = [3; 5]; // 等同于 let a = [3, 3, 3, 3, 3];
这里 a
是一个整型数组,包含五个都是数字3的元素。
通过索引来访问数组中的元素。
let first = a[0]; // 访问第一个元素
let second = a[1]; // 访问第二个元素
在访问时,如果索引超出了数组的边界,Rust 会在编译时或运行时(取决于索引是否为常量表达式)抛出错误,这是 Rust 的安全性特性之一。
元组非常适合用于有结构的数据,而数组非常适合用于有相同数据类型要求的连续数据序列。通过使用元组和数组,可以创建出符合你需要的各种数据结构。
自定义类型主要指的是通过使用结构体(struct
)和枚举(enum
)来创建的数据类型。这两种类型允许开发者定义和使用更丰富且符合业务逻辑的数据结构。
结构体是将零个或多个不同类型的数据聚合到一个复合类型中的一种方式。他们在概念上类似于其他语言中的类(但没有继承功能),是用于创建自定义数据类型的集合。
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
在这个例子中,User
结构体包含了四个字段,每个字段都有指定的类型。
要使用结构体,你需要创建其实例并为其字段提供相应的值。
let user1 = User {
email: String::from("[email protected]"),
username: String::from("ziyang"),
active: true,
sign_in_count: 1,
};
可以通过派生特性 #[derive(Debug)]
来允许结构体实例在使用 println!
宏时使用 {:?}
或 {:#?}
格式化输出。
枚举允许定义一个类型,它可以是几个不同的变体中的一个。枚举在那些一次只能有一个值从多个可能的值中选取的情况下特别有用。
enum IpAddrKind {
V4,
V6,
}
这里,IpAddrKind
枚举有两个变体:V4
和 V6
。
枚举也可以关联数据。
enum IpAddr {
V4(String),
V6(String),
}
甚至每个枚举变体关联的数据都可以有不同类型。
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
用 match
控制流运算符来处理枚举有助于确保你考虑到了所有可能的情况。
!
)Rust 中有一个表示"永不返回"的特殊类型,称作 !
类型,也就是所谓的 “Never” 类型。这个类型用于那些不返回任何值的函数,或者那些不会正常返回,因为它们会无限循环或者结束当前进程的函数。
Never 类型用于表示永不返回的函数。这有助于 Rust 进行更严格的类型检查和控制流分析。
示例:
fn forever() -> ! {
loop {
// 无限循环,永不返回
}
}
在这个示例中,forever
函数有一个 !
返回类型,表示此函数将永远不会返回一个值。
!
) 的用途控制流运算符:
!
类型主要与 Rust 中的 match
表达式一起用于保证所有可能情况都已处理。如果 match
的一个分支结束于一个永不返回的函数,Rust 知道不需要返回值。这就是所谓的"穷尽性检查"(exhaustiveness checking)。
错误处理:
经常与 panic!
宏一起使用,它会使当前线程崩溃,并可以带有一个错误消息。由于 panic!
永远不会返回,它的返回类型是 !
。
本章深入讲解了 Rust 中的标量类型、复合类型、自定义类型以及特殊的 never 类型。此外,还有指针类型、动态大小类型、函数类型等其他重要的数据类型将在后续文章中陆续进行逐一介绍。