这是本节的多页打印视图。
点击此处打印.
返回本页常规视图.
Rust的数据类型
Rust的数据类型
Rust 是 静态类型(statically typed)语言,在编译时必须知道所有变量的类型。在 Rust 中,每一个值有明确的 数据类型(data type),以便明确数据处理方式。
Rust 有两类数据类型子集:标量(scalar)和复合(compound)
标量类型
标量(scalar)类型代表一个单独的值。
Rust 有四种基本的标量类型:
- 整型(integers)
- 浮点型(floating-point numbers)
- 布尔类型(Booleans)
- 字符(characters)
复合类型
复合类型(Compound types)可以将多个值组合成一个类型。
Rust 有两个原生的复合类型:
- 元组(tuple)
- 数组(array)
1 - Rust的整型类型
Rust的整型类型
Rust 中的整型
Rust 内建的整数类型。在有符号列和无符号列中的每一个变体(例如,i16
)都可以用来声明整数值的类型。
长度 |
有符号 |
无符号 |
8-bit |
i8 |
u8 |
16-bit |
i16 |
u16 |
32-bit |
i32 |
u32 |
64-bit |
i64 |
u64 |
arch |
isize |
usize |
注意:isize
和 usize
类型依赖运行程序的计算机架构:64 位架构上它们是 64 位的, 32 位架构上它们是 32 位的。
整型字面值
可以使用表格中的任何一种形式编写数字字面值。注意除 byte 以外的所有数字字面值允许使用类型后缀,例如 57u8
,同时也允许使用 _
做为分隔符以方便读数,例如1_000
。
数字字面值 |
例子 |
说明 |
Decimal |
98_222 |
|
Hex |
0xff |
16进制 |
Octal |
0o77 |
8进制 |
Binary |
0b1111_0000 |
2进制 |
Byte (u8 only) |
b'A' |
字节字面量 |
那么该使用哪种类型的数字呢?如果拿不定主意,Rust 的默认类型通常就很好,数字类型默认是 i32
:它通常是最快的,甚至在 64 位系统上也是。isize
或 usize
主要作为某些集合的索引。
整型溢出
“整型溢出”(“integer overflow” )在 Rust 中有一些有趣的规则:
-
当在 debug 模式编译时,Rust 检查这类问题并使程序 panic
-
在 release 构建中,Rust 不检测溢出,相反会进行一种被称为 “two’s complement wrapping” 的操作。
简而言之,256
变成 0
,257
变成 1
,依此类推。
依赖溢出被认为是一种错误,即便可能出现这种行为。如果你确实需要这种行为,标准库中有一个类型显式提供此功能,Wrapping
。
2 - Rust的浮点型
Rust的浮点类型
Rust 有两个原生的 浮点数 f32
和 f64
,分别占 32 位和 64 位。
默认类型是 f64
,因为在现代 CPU 中,它与 f32
速度几乎一样,不过精度更高。
let num = 3.1415926f64;
assert_eq!(-3.14, -3.14f64);
assert_eq!(2., 2.0f64);
assert_eq!(2e4, 20000f64);
特殊值:
use std::f32::{INFINITY, NEG_INFINITY, NAN, MIN, MAX};
println!("{:?}", INFINITY);
println!("{:?}", NEG_INFINITY);
println!("{:?}", NAN);
println!("{:?}", MIN);
println!("{:?}", MAX);
打印结果为:
inf
-inf
NaN
-340282350000000000000000000000000000000.0
340282350000000000000000000000000000000.0
3 - Rust的布尔型
Rust的布尔类型
Rust 内置布尔类型
Rust 中的布尔类型使用 bool
表示,可以通过as操作将bool转为数字0和1,但是不支持从数字转为bool:
fn main() {
let _t = true;
// 显式指定类型注解
let _f: bool = false;
// 用 as 转成 int
let i:i32 = _f as i32;
print!("{}", i);
}
使用布尔值的主要场景是条件表达式,例如 if
表达式。
标准库
https://doc.rust-lang.org/std/primitive.bool.html
bool代表一个值,它只能是true或false。如果你把bool 转为整数,那么true将是1,false将是0。
bool实现了各种 trait ,如BitAnd、BitOr、Not等,这些特征允许我们使用&、|和 ! 来执行布尔运算。
assert! 是测试中的一个重要的宏,用于检查一个表达式是否返回真值。
let bool_val = true & false | false;
assert!(!bool_val);
4 - Rust的字符型
Rust的字符类型
使用单引号来定义字符类型。
Rust 的 char
类型代表了一个 Unicode 标量值(Unicode Scalar Value),每个字符占4个字节。
fn main() {
let x = 'r';
let x = 'Ú';
// 支持转义
println!("{}", '\'');
println!("{}", '\\');
println!("{}", '\n');
println!("{}", '\r');
println!("{}", '\t');
// 用 ASCII 码表示字符
assert_eq!('\x2A', '*');
assert_eq!('\x25', '%');
// 用 unicode 表示字符
assert_eq!('\u{CA0}', 'ಠ');
assert_eq!('\u{151}', 'ő');
// 可以使用 as 操作符将字符转为数字类型
assert_eq!('%' as i8, 37);
assert_eq!('ಠ' as i8, -96); //该字符值的高位会被截断,最终得到-96
}
5 - Rust的never类型
Rust的never类型
Rust 的 never 类型( !
)用于表示永远不可能有返回值的计算类型。
Rust 是一个类型安全的语言,所以需要将没有返回值的情况(如线程退出)纳入类型管理。
#![feature(never_type)]
let x:! = {
return 123
};
报错:
error[E0554]: #![feature] may not be used on the stable release channel
--> src/main.rs:1:1
|
1 | #![feature(never_type)]
| ^^^^^^^^^^^^^^^^^^^^^^^
never 是试验特性,需要使用 nightly 版本。
6 - Rust的元组(Tuple)类型
Rust的元组(Tuple)类型
元组(tuple)是一种异构有限序列:
- 异构 指元组内的元素可以是不同的类型
- 有限 是指元组有固定长度
创建元组
使用包含在圆括号中的逗号分隔的值列表来创建一个元组。元组中的每一个位置都有一个类型,而且这些不同值的类型也不必是相同的:
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
tup
变量绑定到整个元组上,因为元组是一个单独的复合元素。
元组取值
为了从元组中获取单个值,可以使用模式匹配(pattern matching)来解构元组值:
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {}", y);
}
除了使用模式匹配解构外,也可以使用点号(.
)后跟值的索引来直接访问它们:
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。
特殊元组
当元组中只有一个元素时,需要加逗号,即 (1,)
空元组,`()`
7 - Rust的数组(Array)类型
Rust的数组(Array)类型
数组(array)与元组不同,数组中的每个元素的类型必须相同。数组的特点是:
可以通过 let mut
关键字定义可变绑定的 mut_arr,但是也只能通过下标修改数组元素的值。
数组的类型签名为 [T; N]:
- T 是泛型标记,代表数组中元素的具体类型
- N 是数组长度,是一个 编译时常量,必须在编译时确认值,而且不可改变。
Rust 中数组的定义和使用方式:
// 声明数组,默认不可变
let arr: [i32; 3] = [1, 2, 3];
// 声明可变数组
let mut mut_arr = [1, 2, 3];
assert_eq!(1, mut_arr[0]);
// 通过下标修改可变数组元素的值
mut_arr[0] = 3;
assert_eq!(3, mut_arr[0]);
// 创建初始值为0大小为10的数组
let init_arr = [0; 10];
assert_eq!(0, init_arr[5]);
assert_eq!(10, init_arr.len());
// 下标越界
// error: index out of bounds: the len is 3 but the index is 5
println!("{:?}", arr[5]);
如果下标越界,rust会以 panic 的方式报错。
数组内存分配
数组是在栈(stack)而不是在堆(heap)上为数据分配内存空间。
对于原始固定长度数组,只有实现了 Copy trait 的类型才能作为其元素,也就是说,只有可以在栈上存放的元素才可以存放在该类型的数组中。
未来,rust将支持VLA(variable-length array) 数组,即可变长度数组。
8 - Rust的范围(Range)类型
Rust的范围(Range)类型
Rust 内置的范围类型,包括 左闭右开 和 全币 两种区间,分别是 std::ops::Range 和 std::ops::RangeInclusive 的实例:
// (1..5)是结构体std::ops::Range的一个实例
use std::ops::{Range, RangeInclusive};
assert_eq!((1..5), Range{ start: 1, end: 5 });
// (1..=5)是结构体std::ops::RangeInclusive的一个实例
assert_eq!((1..=5), RangeInclusive::new(1, 5));
// 自带的 sum 方法用于求和
assert_eq!(3+4+5, (3..6).sum());
assert_eq!(3+4+5+6, (3..=6).sum());
(3..6)
// 每个范围都是一个迭代器,可用for 循环打印范围内的元素
for i in (1..5) {
println!("{}", i);
}
for i in (1..=5) {
println!("{}", i);
}
9 - Rust的切片(Slice)类型
Rust的切片(Slice)类型
Slice 切片是对一个数组(包括固定大小数组和动态数组)的引用片段,可以安全访问数组的一部分,而不需要拷贝。
在底层,切片表示为一个指向数组起始位置的指针和数组长度。
// 固定大小数组的切片
let arr: [i32; 5] = [1, 2, 3, 4, 5];
assert_eq!(&arr, &[1,2,3,4,5]);
assert_eq!(&arr[1..], [2,3,4,5]);
assert_eq!((&arr).len(), 5);
assert_eq!((&arr).is_empty(), false);
// 可变数组的切片
let arr = &mut [1, 2, 3];
arr[1] = 7;
assert_eq!(arr, &[1, 7, 3]);
//使用 vec! 宏定义的动态数组的切片
let vec = vec![1, 2, 3];
assert_eq!(&vec[..], [1,2,3]);
// 字符串数组的切片
let str_slice: &[&str] = &["one", "two", "three"];
assert_eq!(str_slice, ["one", "two", "three"]);
10 - Rust的结构体(Struct)类型
Rust的结构体(Struct)类型
Rust 提供三种结构体:
- Named-Field Struct
- Tuple-Like Struct
- Unit-Like Struct
Named-Field Struct
Named-Field 是最常见的。
#[derive(Debug, PartialEq)]
pub struct People {
name: &'static str,
gender: u32,
} // 注意这里没有分号
impl People {
// new 方法的参数并没有 &self
fn new(name: &'static str, gender: u32) -> Self {
return People { name: name, gender: gender };
}
// 读方法,传递的是 &self 不可变引用
fn name(&self) {
println!("name: {:?}", self.name);
}
// 写方法,传递的是 &mut self 可变引用
fn set_name(&mut self, name: &'static str) {
self.name = name;
}
fn gender(&self) {
let gender = if self.gender == 1 { "boy" } else { "girl" };
println!("gender: {:?}", gender);
}
}
fn main() {
// 用 :: 来调用new方法,默认不可变
let alex = People::new("Alex", 1);
// 调用其他方法用 . 号,不用传递 &self
// 为啥不直接把 &self 改成类型java的this语法呢?反正也不传递
alex.name();
alex.gender();
// 也可以直接构建结构体,绕过new方法
assert_eq!(alex, People { name: "Alex", gender: 1 });
// 创建可变结构体
let mut alice = People::new("Alice", 0);
alice.name();
alice.gender();
assert_eq!(alice, People { name: "Alice", gender: 0 });
// 就可以调用set方法了
alice.set_name("Rose");
alice.name();
assert_eq!(alice, People { name: "Rose", gender: 0 });
}
结构体名字要用驼峰法。
Tuple-Like Struct
元组结构体像元组和结构体的混合体:字段没有名字,只有类型:
#[derive(Debug, PartialEq)]
struct Color(i32, i32, i32); // 注意这里要有分号!
fn main() {
// 直接构造,不用new方法
let color = Color(0, 1, 2);
assert_eq!(color.0, 0);
assert_eq!(color.1, 1);
assert_eq!(color.2, 2);
}
使用 .
号按下标访问字段。
当元组结构体只有一个字段的时候,称为 New Type 模式:
#[derive(Debug, PartialEq)]
struct Integer(u32);
// 用关键字 type 为i32类型创建别名Int
type Int = i32;
fn main() {
let int = Integer(10);
assert_eq!(int.0, 10);
let int: Int = 10;
assert_eq!(int, 10);
}
Unit-Like Struct
单元结构体是没有任何字段的结构体。
// 等价于 struct Empty {}
struct Empty;
let x = Empty;
println!("{:p}", &x);
let y = x;
println!("{:p}", &y as *const _);
let z = Empty;
println!("{:p}", &z as *const _);
// struct RangeFull; // 标准库源码中RangeFull就是一个单元结构体
assert_eq!((..), std::ops::RangeFull); // RangeFull就是(..),表示全范围
单元结构体和 new type 模式类似,也相当于定义了一个新的类型。
单元结构体一般用于特定场景,标准库源码中RangeFull就是一个单元结构体。
参考资料
视频:
11 - Rust的枚举(Enum)类型
Rust的枚举(Enum)类型
枚举用 enum 关键字定义,有三种类型。
无参数枚举体
enum Number {
Zero,
One,
Two,
}
let a = Number::One;
match a {
Number::Zero => println!("0"),
Number::One => println!("1"),
Number::Two => println!("2"),
}
类C枚举
enum Color {
Red = 0xff0000,
Green = 0x00ff00,
Blue = 0x0000ff,
}
println!("roses are #{:06x}", Color::Red as i32);
println!("violets are #{:06x}", Color::Blue as i32);
带参数枚举
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let f: fn(u8, u8, u8, u8) -> IpAddr = IpAddr::V4;
let ff: fn(String) -> IpAddr = IpAddr::V6;
let home = IpAddr::V4(127, 0, 0, 1);
带参数枚举的值本质上属于函数指针类型:
- fn(u8, u8, u8, u8) -> IpAddr
- fn(String) -> IpAddr
参考资料
视频: