Rust有两种数据类型:标量值(scalar)和复合值(compound)。
本篇环境为Ubuntu22.04,x86_64,Rust版本为1.60.0。
标量值
标量类型标识一个单独的值,Rust有4种主要的标量类型:整型、浮点型、布尔类型、字符类型。
这四种类型与Perl中的Scalar Literals概念类似,都是字面值,所见即所得。
Rust默认数据类型为4字节的i32,与C/C++保持一致。
整型
与C/C++中定义相同,分为有符号与无符号两种,支持加、减、乘、除、模运算。
整数除法与C/C++一致,向下取余,例如2/3 == 0。
字节数 | 比特数 | Rust | C/C++ |
---|---|---|---|
1 | 8 | i8/u8 | char/unsigned char |
2 | 16 | i16/u16 | short/unsigned short |
4 | 32 | i32/u32 | int/unsigned int |
8 | 64 | i64/u64 | long long/unsigned long long |
4/8 | 32/64 | isize/usize | long/unsigned long |
isize和usize属于自适应类型,根据架构来决定大小,在32位架构就是4字节,64位架构为8字节,对应gcc编译器下的long和unsigned long类型,微软的msvc编译器没有这一特性,需要用宏来判断。自适应类型非常好用,尤其是在C/C++中用内存地址与指针进行互转时。
或许MSVC不支持自适应类型的原因之一就是Windows只能在有限的架构上运行?(笑)
整型表示
为了便于阅读,不用怼着屏幕数数字个数,Rust可以使用’_'字符作为数字的分隔符。
字面值 | 例子 |
---|---|
十进制 | 98_222 |
十六进制 | 0xff |
八进制 | 0o77 |
二进制 | 0b1111_0000 |
字节 | b’A’ |
字节类型为单字节字符,仅限于u8,与Python中的Bytes完全不同。
整型溢出问题
在debug模式编译下,Rust会检查并使程序panic,而release模式下,Rust不检测溢出,会执行2的补码包装(two’s complement wrapping)操作,例如在u8类型下,值256会变成0,257变成1,即
value = value % 2^(sizeof(value)*8)
整型溢出在Rust中被认为是一种错误,但如果编程时确实需要使用溢出,可以在数字类型上使用标准库中的Wrapping功能:
- 包装所有模式:wrapping_*,例如wrapping_add
- 返回None值:checked_*
- 返回布尔值:overflowing_*,指示是否发生溢出
- 饱和处理:saturating_*,在边界上饱和处理成最大或最小值
饱和处理,对数字值进行计算时,发生上溢时返回最大值,下溢时返回最小值,无符号最小值为0,有符号时值为0 - (2^n-1)。
/* 函数签名 */
pub trait Saturating {
fn saturating_add (self, v: Self) -> Self; // 返回a+b,溢出时返回该类型的最大值
fn saturating_sub (self, v: Self) -> Self; // 返回a-b,溢出时返回该类型的最小值
}
浮点型
Rust浮点类型使用IEEE-754标准表示,有两种浮点类型,f32和f64,分别占4字节和8字节,对应C/C++中的float和double,所有的浮点类型都是有符号的。
Rust认为在现代CPU上f32与f64速度相当,但精度更高,所以默认类型是f64,与Python相同(如果是在32位芯片上,个人还是建议使用f32)。
let x = 2.0; // f64
let y: f32 = 3.0; // f32
布尔型
类型名称为bool,分为true和false,常用于条件表达式中。
字符型
Rust的字符型数据使用4个字节存储,表示为char,与C/C++中一样使用单引号,字符串字面值使用双引号。
字符型表示一个Unicode代码点,范围为U+0000到U+D7FF,U+E000到U+10FFFF。
有点好奇Rust中正则的用法了。
复合类型
Rust有两个原生复合类型:元组(tuple)和数组(array)。
元组类型
元组使用圆括号,是长度固定的数组(C系语言概念),可以包含一个或多个类型的值,第一个元素的索引值为0。
// 示例函数
fn main() {
// 使用类型注解
let tup: (i32, f64, u8) = (500, 6.4, 1);
// 可以省略类型注解
let tup = (500, 6.4, 1);
// 使用模式匹配(pattern matching)对元组进行解构(destructure)
let (x, y, z) = tup;
// 使用点号'.'跟索引值来直接访问
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
}
没有任何元素的元组是一种特殊类型,表示为( ),只有一个值,和表示相同,写成( ),被称为单位类型(unit type),值被称为单位值(unit value)。如果表达式没有显示地返回值,则隐式地返回该单位值。
数组类型
数组使用方括号,Rust中数组长度是固定的,并且每个值的类型都相同。
let a = [1, 2, 3, 4, 5];
let a: [i32; 5] = [1, 2, 3, 4, 5]; // 带注解的数组类型写法
let a = [3, 3, 3, 3, 3,]; // 初始值为5个3
let a = [3; 5]; // 初始值为5个3,与上一行效果相同
// 确定了元素个数不会改变时,建议使用数组
let months = ["January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December"];
// 数组是在栈上分配已知固定大小的内存块
// 可以使用索引访问
let first = a[0];
left second = a[1];
数组的栈溢出
当数组在运行时发生索引溢出时,程序会退出并输出错误信息。Rust在使用数组时,会在编译时和运行时都检查索引是否小于数组长度,当编译时发现溢出,编译器会报错,运行发生溢出时,程序会panic。
此原则会尽量避免像C/Python/Java等语言中忘记检查输入范围而导致栈溢出漏洞情况的出现。
编译时检查:
// 示例代码main.rs
fn main() {
let a = [1, 2, 3, 4, 5];
let five = a[5];
println!(
"The six value of the element at index {}",
five
);
}
进行编译:
xxx@ubuntu:~/Rust/array$ cargo build
Compiling array v0.1.0 (/home/xxx/Rust/array)
error: this operation will panic at runtime
--> src/main.rs:5:16
|
5 | let five = a[5];
| ^^^^ index out of bounds: the length is 5 but the index is 5
|
= note: `#[deny(unconditional_panic)]` on by default
error: could not compile `array` due to previous error
运行时检查:
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 {} is: {}",
index, element
);
}
当编译后运行时,输入的值为10时,报错如下:
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 10', src/main.rs:19:19
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace