Rust 基本类型

kaichikaichi • 2022-01-28

10 分钟0 阅读

Rust 中每个值都有其确切的数据类型,分为两类: 基本类型和复合类型(类似 JavaScript 中的原始数据类型和对象数据类型)。基本类型可以理解为最小化原子类型,而复合类型就是这些基本类型的组合(分子)。基本类型包括:

  • 数值类型:有符号整数(i8,i16,i32,i64,isize)、无符号整数(u8,u16,u32,u64,usize)、浮点数(f32,f64)、有理数和复数
  • 字符串类型:字符串字面量和字符串切片 &str
  • 布尔类型:truefalse
  • 字符类型:单个 Unicode 字符,储存为 4 个字节
  • 单元类型:(),其唯一值也是 ()

类型标注与推导

显式类型标注。

let x: i32 = 1;

// 另外一种方式
let y = 3.0f64;

// 相较于上一种更易读
let z = 2.0_f32;

并不是所有的变量都需要显式指定类型,对于一些可以根据变量的值和上下文中的使用方式推导的变量可以不用类型标注。

let a = 2.0; // 推导为 f64 类型

但是并不是都能推导出变量类型,那么就需要进行手动标注。

// 错误: type annotations needed
let guess = "42".parse().expect("Not a number!");
// 正确
let guess: i32 = "42".parse().expect("Not a number!");

这种情况编译器无法推导 guess 的类型,guess 可能为很多种类型(整数?浮点数?字符串?),因此需要手动标注类型。

类型

数值类型

整数类型

整数是没有小数部分的数字。前面看到的 i32 类型表示有符号 32 整数(i 表示 integer,与之相反的是 u(unsigned),代表无符号)。Rust 中的内置整数类型。

长度有符号类型无符号类型
8 位i8u8
16 位i16u16
32 位i32u32
64 位i64u64
128 位128u128
视架构而定isizeusize

有符号类型数字范围是 -(2n-1) ~ 2n-1-1,其中 n 是定义的位长度。例如 i8 可存储的数字范围是 -128 ~ 127。无符号类型可存储的数字范围是 0 ~ 2n - 1,所以 u8 可存储的数字范围是 0 ~ 255。

isizeusize 类型取决于计算机 CPU 类型,32 位 CPU 就是 32 位,同理,64 位 CPU 就是 64 位。

整型字面量书写形式。

数字字面量示例
十进制98_222
十六进制0xff
八进制0o77
二进制0b1111_0000
字节(仅限 u8b'A'

浮点类型

浮点类型包括两种基本类型:f32(单精度) 和 f64(双精度),分别为 32 位和 64 位大小。默认类型为 f64,现代 CPU 中速度和 f32 几乎相同,但精度更高。浮点数根据 IEEE-754 标准实现。

let x = 2.0; // f64

let y: f32 = 3.0; // f32

NaN

作为 JavaScript 的使用者应该不陌生。NaN(Not a Number)表示数学上未定义的结果。和 NaN 进行运算其结果也是 NaN,且 NaN 不能进行比较(各有各的产生方式)。

let x = (-42.0_f32).sqrt(); // x => NaN
assert_eq!(x, x); // 错误

可以使用 is_nan() 判断一个数值是否是 NaN

let x = (-42.0_f32).sqrt();
if x.is_nan() {
    println!("未定义的数学行为")
}

序列

Rust 提供生产连续数值的简单方式,例如 1..5,生成 [1,5) 的连续数字。1..=5 生成 [1,5] 的连续数字,通常用于循环。

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

打印 1 ~ 5。序列也可以用于字符类型,且仅可用于数字和字符类型。

for i in 'a'..='z' {
  println!("{}", i);
}

字符类型

所有 Unicode 值都可以作为 Rust 字符。

let c = 'z';
let g = '国';
let heart_eyed_cat = '😻';

Unicode 是 4 个字节编码,所以字符类型也占用 4 个字节。

let g = '国';
println!("字符 '国' 占用 {} 字节", std::mem::size_of_val(&g));
// -> 字符 '国' 占用 4 字节

布尔类型

布尔类型有两个值:truefalse,布尔值占 1 字节,布尔值通常用于控制流程。

let t = true;

let f: bool = false;

if f {
  println!("不会执行");
}

单元类型

单元类型是 (),其唯一的值也是 (),是一个零长元组。fn main() 函数的返回值就是它。不能说 main 没有返回值,因为没有返回值的函数在 Rust 中是单独定义的,叫发散函数,是一种无法收敛的函数,println!() 的返回值也是单元类型 ()

可以使用 () 来占位,完全不占用内存。

语句和表达式

语句和表达式在 Rust 中有明确的区分,其他语言往往不区分。用 js 的方式类比(注意 ⚠️:下面是 js 代码),通常是不做区分的。

let a = 1; // 可以看作语句
//控制台输出 undefined
a = 2; // 可以看作表达式
//控制台输出 2,`=` 赋值运算符是有返回值的

语句

let a = 8;
let b: Vec<f64> = Vec::new();
let (a, c) = ("hi", false);

以上几行均是语句,执行操作但是没有返回值。

let b = (let a = 8);
// 错误:`let` 用法目前是试验性的,在稳定版中尚不能使用

let 是语句,不是表达式,没有返回值,所以不能用来赋值。以后可能可以。

表达式

表达式会进行求值,5 + 6 就是一个表达式,求值后返回 11, 所以可以用来赋值。

let a = 5 + 6;

let b = 6;

6 也是一个表达式,求值后返回值 6

调用函数是表达式,因为会返回一个值,调用宏也是表达式,用花括号包裹有返回值的语句块也是表达式,总而言之,能返回值就是表达式。

let y = {
  let x = 3;
  x + 1
}

println!("y is {}", y); // -> y is 4

注意:x + 1 不能以分号结尾,否则就是语句,就不会返回值,表达式不能有分号

函数

fn add(i: i32,j: i32) -> i32 {
  i + j
}

这里是 add 函数,虽然简单,但是包含函数的所有必要元素。

  • 定义函数的关键字 fn
  • 函数名 add
  • 标明类型的函数参数 ij
  • i32 类型的函数返回值
  • 函数体使用花括号包裹
  • i + j 表达式返回值

这大概就是函数的所有,还有一些细节需要注意。

  • 函数名和参数名遵从蛇形命名法,例如 fn add_one() -> {}
  • 函数定义的位置无关紧要,定义了即可(js 的函数提升)

上面的例子中在函数体最后一行通过表达式返回值,当然也可以使用 return 返回。

fn plus_or_sub(x: i32) -> i32 {
  if x > 5 {
    return x - 1
  }

  x + 1
}

特殊返回值

无返回值 ()

前面单元类型有涉及到作为返回值的应用。

  • 函数没有返回值,那么返回一个 ()
  • 分号结尾的表达式返回一个 ()
fn add(i: i32,j: i32) -> i32 {
  i + j;
}

add(1, 2); // 错误:类型不匹配

例如前面的 add 函数,如果 i + j 加上了分号结尾,就会返回 (),单元类型并不是期望的 i32 类型,就会报错。所以在 Rust 中,一定要严格区分表达式语句

永不返回 !

感叹号标注函数返回值表示该函数永远不会返回,这种语法往往导致程序奔溃。

fn forever() -> ! {
  loop {
    // 做些啥
  }
}

fn dead_end() -> ! {
  panic!("panic!!!!");
}
在 Github 上编辑