这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

Rust的数据类型

Rust的数据类型

Rust 是 静态类型statically typed)语言,在编译时必须知道所有变量的类型。在 Rust 中,每一个值有明确的 数据类型data type),以便明确数据处理方式。

Rust 有两类数据类型子集:标量(scalar)和复合(compound)

标量类型

标量scalar)类型代表一个单独的值。

Rust 有四种基本的标量类型:

  1. 整型(integers)
  2. 浮点型(floating-point numbers)
  3. 布尔类型(Booleans)
  4. 字符(characters)

复合类型

复合类型Compound types)可以将多个值组合成一个类型。

Rust 有两个原生的复合类型:

  1. 元组(tuple)
  2. 数组(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

注意:isizeusize 类型依赖运行程序的计算机架构: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 位系统上也是。isizeusize 主要作为某些集合的索引。

整型溢出

“整型溢出”(“integer overflow” )在 Rust 中有一些有趣的规则:

  • 当在 debug 模式编译时,Rust 检查这类问题并使程序 panic

  • 在 release 构建中,Rust 不检测溢出,相反会进行一种被称为 “two’s complement wrapping” 的操作。

    简而言之,256 变成 0257 变成 1,依此类推。

    依赖溢出被认为是一种错误,即便可能出现这种行为。如果你确实需要这种行为,标准库中有一个类型显式提供此功能,Wrapping

2 - Rust的浮点型

Rust的浮点类型

Rust 有两个原生的 浮点数 f32f64,分别占 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

参考资料

视频: