数据类型
Rust 中的每个值都属于某种数据类型,它告诉 Rust 什么样的 data 是指定的,因此它知道如何处理该数据。我们将看看 两个数据类型子集:Scalar 和 Compound。
请记住,Rust 是一种静态类型语言,这意味着它
必须在编译时知道所有变量的类型。编译器通常可以
根据值以及我们如何使用它来推断我们想要使用的类型。在以下情况下
当可以使用多种类型时,例如当我们将 a 转换为数字时
在 “Comparing the Guess to the Secret (比较猜测与秘密)
Number“ 部分
第 2 章,我们必须添加一个类型注解,像这样:String
parse
#![allow(unused)] fn main() { let guess: u32 = "42".parse().expect("Not a number!"); }
如果我们不添加上述代码中所示的类型注解,则 Rust
将显示以下错误,这意味着编译器需要更多
来自我们的信息以了解我们想要使用哪种类型:: u32
$ cargo build
Compiling no_type_annotations v0.1.0 (file:///projects/no_type_annotations)
error[E0284]: type annotations needed
--> src/main.rs:2:9
|
2 | let guess = "42".parse().expect("Not a number!");
| ^^^^^ ----- type must be known at this point
|
= note: cannot satisfy `<_ as FromStr>::Err == _`
help: consider giving `guess` an explicit type
|
2 | let guess: /* Type */ = "42".parse().expect("Not a number!");
| ++++++++++++
For more information about this error, try `rustc --explain E0284`.
error: could not compile `no_type_annotations` (bin "no_type_annotations") due to 1 previous error
您将看到其他数据类型的不同类型注释。
标量类型
标量类型表示单个值。Rust 有四种主要的标量类型: 整数、浮点数、布尔值和字符。您可能会认出 这些来自其他编程语言。让我们来看看它们在 Rust 中是如何工作的。
整数类型
整数是没有小数分量的数字。我们使用了一个整数
在第 2 章 type 中键入。此类型声明表示
值应为无符号整数(有符号整数类型
start with 而不是 ),则占用 32 位空间。表 3-1 显示
Rust 中的内置整数类型。我们可以使用这些变体中的任何一个来声明
整数值的类型。u32
i
u
长度 | 签署 | 无符号 |
---|---|---|
8 位 | i8 | u8 |
16 位 | i16 | u16 |
32 位 | i32 | u32 |
64 位 | i64 | u64 |
128 位 | i128 | u128 |
拱 | isize | usize |
每个变体可以是 signed 或 unsigned,并且具有显式大小。Signed 和 unsigned 是指数字是否可以 negative - 换句话说,数字是否需要带有符号 (签名),或者它是否只会是正的,因此可以是 无符号表示 (unsigned)。这就像在纸上写数字:当 符号很重要,数字用加号或减号显示;然而 当可以安全地假设该数字为正数时,它将不带符号显示。 有符号数字使用 2 的补码表示形式进行存储。
每个有符号变体都可以存储 -(2n - 1) 设置为 2n -
1- 1 (含),其中 n 是 variant 使用的位数。所以 an 可以存储 -(2i8
7) 设置为 27- 1,等于
-128 到 127。无符号变体可以存储 0 到 2 之间的数字n- 1,
所以 A 可以存储从 0 到 2 的数字u8
8- 1,等于 0 到 255。
此外,and 类型取决于
运行程序的计算机,在表中表示为 “Arch”:
64 位(如果使用的是 64 位体系结构)和 32 位(如果使用的是 32 位体系结构)
建筑。isize
usize
您可以用表 3-2 中所示的任何形式编写整数文本。注意
可以是多个数字类型的数字文本允许类型后缀
例如 ,以指定类型。数字字面量也可以用作
可视分隔符使数字更易于阅读,例如 ,它将
的值与指定的值相同。57u8
_
1_000
1000
数字文本 | 例 |
---|---|
十进制 | 98_222 |
十六进制 | 0xff |
八进制 | 0o77 |
二元的 | 0b1111_0000 |
字节 (仅)u8 | b'A' |
那么你怎么知道该使用哪种类型的整数呢?如果您不确定,Rust 的
默认值通常是很好的起点:整数类型默认为 。
在编制索引时使用 or is 的主要情况
某种集合。i32
isize
usize
整数溢出
假设你有一个类型的变量,它可以保存 0 到
255. 如果您尝试将变量更改为该范围之外的值,例如
256 时,将发生整数溢出,这可能导致以下两种行为之一。
当你在调试模式下编译时,Rust 包括对整数溢出的检查
如果发生此行为,则会导致程序在运行时出现 panic。锈
使用术语 panicking 当程序退出并出现错误时;我们将讨论
在章节的 “Unrecoverable Errors with panic!
” 一节中更深入地了解 panic
9.u8
当你使用该标志在发布模式下编译时,Rust 不包括对导致 panic 的整数溢出的检查。相反,如果
overflow 发生时,Rust 执行 2 的补码包装。简而言之,价值观
大于类型可以将 “wrap around” 保持为最小值的最大值
类型可以保存的值。在 a 的情况下,值 256 变为
0,则值 257 变为 1,依此类推。程序不会 panic,但
variable 的值可能不是你期望的值
有。依赖整数溢出的换行行为被视为错误。--release
u8
要显式处理溢出的可能性,您可以使用这些系列 标准库为原始数字类型提供的方法:
- 使用方法(如 .
wrapping_*
wrapping_add
- 如果方法溢出,则返回该值。
None
checked_*
- 返回值和一个布尔值,指示是否存在 overflow with
方法。
overflowing_*
- 使用方法在值的最小值或最大值处饱和。
saturating_*
浮点类型
Rust 还有两种浮点数的原始类型,它们是
带小数点的数字。Rust 的浮点类型是 和 ,
它们的大小分别为 32 位和 64 位。默认类型是因为在现代 CPU 上,它的速度大致相同,但能够
更精确。所有浮点类型都已签名。f32
f64
f64
f32
下面是一个显示浮点数运行中的示例:
文件名: src/main.rs
fn main() { let x = 2.0; // f64 let y: f32 = 3.0; // f32 }
浮点数根据 IEEE-754 标准表示。该类型是单精度浮点数,并且具有双精度。f32
f64
数值运算
Rust 支持你期望的所有数字的基本数学运算
类型:加法、减法、乘法、除法和余数。整数
除法将向零截断为最接近的整数。以下代码显示了
如何在语句中使用每个数值运算:let
文件名: src/main.rs
fn main() { // addition let sum = 5 + 10; // subtraction let difference = 95.5 - 4.3; // multiplication let product = 4 * 30; // division let quotient = 56.7 / 32.2; let truncated = -5 / 3; // Results in -1 // remainder let remainder = 43 % 5; }
这些语句中的每个表达式都使用数学运算符,并计算 转换为单个值,然后将其绑定到变量。附录 B 包含 Rust 提供。
布尔类型
与大多数其他编程语言一样,Rust 中的 Boolean 类型有两种可能
值: 和 .布尔值的大小为 1 个字节。中的 Boolean 类型
Rust 是使用 .例如:true
false
bool
文件名: src/main.rs
fn main() { let t = true; let f: bool = false; // with explicit type annotation }
使用布尔值的主要方式是通过条件,例如表达式。我们将在 Rust 中的“Control
Flow“部分。if
if
字符类型
Rust 的类型是该语言最原始的字母类型。以下是
声明值的一些示例:char
char
文件名: src/main.rs
fn main() { let c = 'z'; let z: char = 'ℤ'; // with explicit type annotation let heart_eyed_cat = '😻'; }
请注意,我们使用单引号指定 Literals,而不是 string
Literals 的 Literals,它使用双引号。Rust 的类型大小为 4 个字节,
表示 Unicode 标量值,这意味着它可以表示比
只是 ASCII。重音字母;中文、日文和韩文字符;表情符号;
和零宽度的空格都是 Rust 中的有效值。Unicode 标量
值范围从 to 和 to including (包括 to 和 to)。
但是,“字符”在 Unicode 中并不是一个真正的概念,因此您的人类
对“字符”是什么的直觉可能与 A 的含义不匹配
锈。我们将在 “存储 UTF-8 编码文本
字符串”。char
char
char
U+0000
U+D7FF
U+E000
U+10FFFF
char
化合物类型
复合类型可以将多个值分组为一种类型。Rust 有两个 原始复合类型:元组和数组。
元组类型
元组是将多个值组合在一起的通用方法,其中 将多种类型合并为一种化合物类型。元组具有固定长度:一次 声明,它们的大小不能增大或缩小。
我们通过在其中编写一个逗号分隔的值列表来创建一个元组 括弧。元组中的每个位置都有一个类型,而 Tuples 中的不同值不必相同。我们添加了可选的 type annotations 中:
文件名: src/main.rs
fn main() { let tup: (i32, f64, u8) = (500, 6.4, 1); }
该变量绑定到整个元组,因为元组被视为
单个复合元素。要从元组中获取单个值,我们可以
使用 pattern matching 来解构 Tuples 值,如下所示:tup
文件名: src/main.rs
fn main() { let tup = (500, 6.4, 1); let (x, y, z) = tup; println!("The value of y is: {y}"); }
该程序首先创建一个元组并将其绑定到变量 。然后
使用一个 pattern with 将其转换为三个单独的
变量 、 、 和 .这称为解构,因为它会中断
单个元组分为三个部分。最后,程序打印 的值 ,即 。tup
let
tup
x
y
z
y
6.4
我们也可以通过使用句点 () 后跟
我们想要访问的值的索引。例如:.
文件名: src/main.rs
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; }
该程序创建元组,然后访问元组的每个元素
使用它们各自的索引。与大多数编程语言一样,第一个
index 为 0。x
没有任何值的元组具有特殊名称 unit。此值及其
相应的类型都写入并表示空值或
空返回类型。如果表达式不这样做,则隐式返回 unit 值
返回任何其他值。()
数组类型
拥有多个值的集合的另一种方法是使用数组。与 一个 Tuples,数组的每个元素都必须具有相同的类型。与 其他一些语言中,Rust 中的数组具有固定的长度。
我们将数组中的值写成 square 内的逗号分隔列表 括弧:
文件名: src/main.rs
fn main() { let a = [1, 2, 3, 4, 5]; }
当您希望在堆栈上分配数据时,数组非常有用,与 到目前为止我们看到的其他类型的类型,而不是堆(我们将讨论 stack 和堆的更多信息)或 您希望确保始终具有固定数量的元素。数组不是 flexible 作为 vector 类型。向量是一种类似的集合类型 由允许增大或缩小大小的 Standard 库提供。如果 你不确定是使用数组还是向量,你很可能应该使用 向量。第 8 章更详细地讨论了向量。
但是,当您知道元素的数量不会 需要改变。例如,如果您在 程序,您可能会使用数组而不是向量,因为您知道 它将始终包含 12 个元素:
#![allow(unused)] fn main() { let months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; }
您可以使用方括号编写数组的类型,其中包含每个元素的类型。 一个分号,然后是数组中的元素数,如下所示:
#![allow(unused)] fn main() { let a: [i32; 5] = [1, 2, 3, 4, 5]; }
这里, 是每个元素的类型。分号后的数字表示数组包含 5 个元素。i32
5
您还可以通过以下方式初始化数组以包含每个元素的相同值 指定初始值,后跟分号,然后指定 方括号中的数组,如下所示:
#![allow(unused)] fn main() { let a = [3; 5]; }
名为 的数组将包含最初将全部设置为 value 的元素。这与写入相同,但在
更简洁的方式。a
5
3
let a = [3, 3, 3, 3, 3];
访问 Array 元素
数组是已知固定大小的单个内存块,可以是 分配。您可以使用索引访问数组的元素, 喜欢这个:
文件名: src/main.rs
fn main() { let a = [1, 2, 3, 4, 5]; let first = a[0]; let second = a[1]; }
在此示例中,名为 的变量将获得值,因为
是数组中 index 处的值。名为 的变量将获取
数组中 index 的值。first
1
[0]
second
2
[1]
无效的数组元素访问
让我们看看如果你尝试访问一个 数组的末尾。假设你运行这段代码,类似于 第 2 章,从用户那里获取数组索引:
文件名: src/main.rs
use std::io;
fn main() {
let a = [1, 2, 3, 4, 5];
println!("Please enter an array index.");
let mut index = String::new();
io::stdin()
.read_line(&mut index)
.expect("Failed to read line");
let index: usize = index
.trim()
.parse()
.expect("Index entered was not a number");
let element = a[index];
println!("The value of the element at index {index} is: {element}");
}
此代码编译成功。如果使用 和 运行此代码
输入 、 、 、 、 或 ,程序会打印出相应的
value 的值。如果您输入的数字超过
数组(如 ),您将看到如下输出:cargo run
0
1
2
3
4
10
thread 'main' panicked at src/main.rs:19:19:
index out of bounds: the len is 5 but the index is 10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
该程序在使用无效的
值。程序退出并显示错误消息,并且
没有执行最终语句。当您尝试访问
元素中,Rust 将检查你指定的索引是否小于
比数组长度。如果索引大于或等于长度,则
Rust 会 panic。此检查必须在运行时进行,尤其是在这种情况下,
因为编译器不可能知道用户在
稍后运行代码。println!
这是 Rust 内存安全原则的一个例子。在许多 低级语言时,不会进行这种检查,当您提供 索引不正确,可以访问无效的内存。Rust 可以保护您免受这种情况的影响 类型的错误,而不是允许内存访问 继续。第 9 章讨论了更多 Rust 的错误处理以及如何 编写可读、安全的代码,既不会 panic 也不允许无效的内存访问。
本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准