基本类型

Rust 的每个值都有其确切的主句类型,总的来说可以分为两类: 基本类型和复合类型。 基本类型意味着他们往往是一个最小化原子类型,无法解构为其他类,一般来说由以下的值组成:

  • 有符号整数 i8 i16 i32 i64 i128 isize
  • 无符号整数 u8 u16 u32 u64 u128 usize
  • 浮点数 f32 f64
  • 字符串切片 &str
  • 布尔类型 bool
  • 字符类型 char 表示单个Unicode字符,存储为4个字节
  • 单元类型 () 也叫做unit类型,表示没有值的类型,类似于C语言中的void类型。

类型推导和类型标注

rust是静态类型语言,编译器必须在编译期知道所有变量的类型。 Rust编译器很聪明,可以根据变量的值和上下文中的食用方式来自动推导出变量的类型。

1
let guess = "42".parse().expect("Not a number!");

这段代码的目的是对字符串42进行解析,而编译器在这里无法推导出我们想要的类型。因此,我们要给编译器guess变量一个显式的类型标注:
let guess: i32="42".parse().expect("Not a number!");
或者
"42".parse::<i32>().expect("Not a number!");

数值类型

整数类型

有无符号 + 类型大小(位数) 无符号数表示数字只能取整数 or 0, 有符号则表示数字可以取正数、负数和 0

有符号数字类型的范围是 -(2)^(n-1) 到 2^(n-1)-1, 其中 n 是数字的位数。无符号数字类型的范围是 0 到 2^n-1。

整形字面量可以用下表的形式书写:

数字字面量 示例
十进制 98_222
十六进制 0xff
八进制 0o77
二进制 0b1111_0000
字面量 b'A'

rust整形默认使用 i32

整形溢出

在debug模式编译的时候,Rust会检查整形溢出,若存在这些问题,则使程序在编译的时候 panic. (崩溃,Rust 使用这个术语来表明程序因为错误而退出)

在当使用 --release参数进行release构建的时候,Rust不检测移除。 相反,当检测到溢出的时候,RUST会采用 补码循环溢出的规则处理。 大于该类型最大值的数值会被补码转换成改类型能够支持的对应数字的最小值。 256 -> 0, 257 0 -> 1

要显式处理可能的输出,就要采用标准库针对原始数字类型提供的这些方法

  • wrapping_* 方法在所有模式下都按照补码循环溢出规则处理
  • checked_* 方法在溢出时返回 None,而不是 panic
  • overflowing_* 方法在溢出时返回一个元组,包含结果和一个布尔值,表示是否发生了溢出
  • saturating_* 方法在溢出时返回该类型的最大值或最小值,而不是 panic
1
2
assert_eq!(100u8.saturating_add(1), 101);
assert_eq(u8::MAX.saturating_add(1), u8::MAX);
1
2
3
4
5
fn main() {
let a: u8 = 255;
let b: a.wrapping_add(20);
println!("{}",b);
}

浮点类型

1
2
3
4
5
6

fn main(){
let x = 2.0; // f64

let y: f32 = 3.0; // f32
}

IEEE-754 定义标准实现。

浮点数陷阱

  1. 浮点数是想要数字的近似表达。
  2. 浮点数在某些特性是反直觉的。 f32,f64上面的比较是 std::cmp::PartialEq 特征,并没有实现std::cmp::Eq特征

Rust 的 HashMap 数据结构,是一个 KV 类型的 Hash Map 实现,它对于 K 没有特定类型的限制,但是要求能用作 K 的类型必须实现了 std::cmp::Eq 特征,因此这意味着你无法使用浮点数作为 HashMap 的 Key,来存储键值对,但是作为对比,Rust 的整数类型、字符串类型、布尔类型都实现了该特征,因此可以作为 HashMap 的 Key。

1
2
3
4
5

fn main(){
// 下面的代码会报错
assert!(0.1 + 0.2 == 0.3);
}

非要比较的话,只能说是这样来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

fn main(){
let abc:(f32,f32,f32) = (0.1,0.2,0.3);
let xyz:(f64,f64,f64) = (0.1,0.2,0.3);

println!("abc (f32)");
println!("0.1 + 0.2 = {}",(abc.0 + abc.1).to_bits());
println!("0.3 = {}",(abc.2).to_bits());
println!();

println!("xyz (f64)");
println!("0.1 + 0.2 = {}",(xyz.0 + xyz.1).to_bits());
println!("0.3 = {}",(xyz.2).to_bits());
println!();

assert!(abc.0+abc.1 == abc.2);
assert!(xyz.0+xyz.1 == xyz.2);
}

,对 f32 类型做加法时,0.1 + 0.2 的结果是 3e99999a,0.3 也是 3e99999a,因此 f32 下的 0.1 + 0.2 == 0.3 通过测试,但是到了 f64 类型时,结果就不一样了,因为 f64 精度高很多,因此在小数点非常后面发生了一点微小的变化,0.1 + 0.2 以 4 结尾,但是 0.3 以3结尾,这个细微区别导致 f64 下的测试失败了,并且抛出了异常。

NaN

对于数学上未定义的结果,例如对负数取平方根,会产生一个特殊的结果: Rust的浮点数类型用 NaN 来处理这些情况

所有的根NaN 交互的结果,都会返回NaN, 并且NaN不能用来比较

1
2
3
4
fn main(){
let x = (-42.0_f32).sqrt();
assert!(x.x);
}

出于防御性的考虑,可以采用is_nan的方法

1
2
3
4
5
6
fn main(){
let x = (-42.0_f32).sqrt();
if x.is_nan() {
println!("x is NaN");
}
}

数字运算

Rust支持所有数字类型的基本数学运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

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;

// remainder
let remainder = 43 % 5;
}

综合性的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
fn main() {
// 编译器会进行自动推导,给予twenty i32的类型
let twenty = 20;
// 类型标注
let twenty_one: i32 = 21;
// 通过类型后缀的方式进行类型标注:22是i32类型
let twenty_two = 22i32;

// 只有同样类型,才能运算
let addition = twenty + twenty_one + twenty_two;
println!("{} + {} + {} = {}", twenty, twenty_one, twenty_two, addition);

// 对于较长的数字,可以用_进行分割,提升可读性
let one_million: i64 = 1_000_000;
println!("{}", one_million.pow(2));

// 定义一个f32数组,其中42.0会自动被推导为f32类型
let forty_twos = [
42.0,
42f32,
42.0_f32,
];

// 打印数组中第一个值,并控制小数位为2位
println!("{:.2}", forty_twos[0]);
}

位运算

运算符 描述
& 按位与,相同位置均为1取1,否则 0
^ 按位异或,相同位置不同取1,否则 0
~ 按位取反,0变1,1变0
! 按位取反,0变1,1变0
<< 左移,低位补0
>> 右移,带符号移动,正数补0,负数补1

对于移位运算,rust会检查它是否超出该整数的位数范围,超出会报错overflow,

1
2
3
4
5
6
7
fn main() {
let a:u8 = 255;
let b = a>>7;
let b = a<<7;
let b = a>>8;
let b = a<<8;
}

序列(Range);

Rust提供了一个非常简洁的方式,用来生成连续的数值,例如 1..5

生成1~4的连续数字,不包含5;

1.. = 5 生成1~5的连续数字,包含5;

1
2
3
for i in 1..=5 {
println!("{}", i);
}

序列只允许用数字和字符类型。

使用As完成类型转换

Rust中可以使用As来完成一个类型到另外一个类型的转换,其最常用于将原始类型转换为其他原始类型。

有理数和复数

Rust的标准库和其他语言的标准库相比,准入门槛比较高,因此有理数和复数没有被包含在标准库中。

  • 有理数和复数
  • 任意大小的整数和任意精度的浮点数
  • 固定精度的十进制小数,常用于货币相关。

如何引入 num库?

  • 创建新的工程 cargo new complex_num && cd complex_num
  • 在Cargo.toml 的 [dependencies] 下添加 num = "0.4.0"

总结

  • Rust数值类型很多
  • 类型转换必须是显示的
  • Rust的数值可以在上面使用方法, 13.14_f32.round()

字符,布尔,单元类型

字符类型 char

1
2
3
4
5
6
fn main(){
let c = 'z';
let z = 'ℤ';
let g = '国';
let heart_eyed_cat = '😻';
}

Rust的字符不仅仅是ASC II , 所有的Unicode值都可以作为Rust字符,包含单个的中文、日文、韩文、emoji符号等等。

char 类型表示一个Unicode 标量值,它占用固定的四字节内存与 UTF-32编码单元一致

1
2
3
4
fn main(){
let x = '中';
println!("字符 '中' 占用了 {} 字节的内存大小",size_of_val(&x));
}

布尔 bool

Rust中的布尔类型有两个可能的值

1
2
3
4
5
6
7
8
fn main(){
let t = true;
let f :bool = false;

if f{
println!("这段代码没有被执行");
}
}

单元类型Unit

单元类型就是 ()

唯一的值也是 ()

main函数就返回这个类型。

没有返回值的函数在rust中是有单独的定义的
发散函数(diverging functions) 顾名思义,无法收敛的函数。

例如常见的println!()的返回值也是单元类型()