bigywater.tech
Rust · 5

基本类型与类型推断

基本类型看起来是最无聊的一章,但很多真实的坑藏在这里:i32 为什么是默认整数、类型推断什么时候放弃、同一份代码在 debug 和 release 下对溢出的反应为什么不同、as 转换到底会丢什么。这篇每条都用真实的编译与运行结果回答。

4 字节
char 的大小(Unicode 标量值),bool 是 1 字节,() 是 0 字节
101 vs 0
255u8 + 1 在 debug 下 panic(退出码 101),release 下回绕成 0
饱和
f64 as i32 对越界值取饱和、NaN 变 0

大小与范围

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
        i8   1 字节  -128 ..= 127
        u8   1 字节  0 ..= 255
       i16   2 字节  -32768 ..= 32767
       u16   2 字节  0 ..= 65535
       i32   4 字节  -2147483648 ..= 2147483647
       u32   4 字节  0 ..= 4294967295
       i64   8 字节  -9223372036854775808 ..= 9223372036854775807
       u64   8 字节  0 ..= 18446744073709551615
      i128  16 字节  -170141183460469231731687303715884105728 ..= 170141183460469231731687303715884105727
      u128  16 字节  0 ..= 340282366920938463463374607431768211455
     isize   8 字节  -9223372036854775808 ..= 9223372036854775807
     usize   8 字节  0 ..= 18446744073709551615
       f32   4 字节
       f64   8 字节
      bool   1 字节
      char   4 字节(Unicode 标量值)
        ()   0 字节

默认类型

实测 · 本机执行输出
  源码:
      1 | fn tn<T>(_: &T) -> &'static str { std::any::type_name::<T>() }
      2 | fn main() {
      3 |     let a = 42;
      4 |     let b = 2.5;
      5 |     let c = true;
      6 |     let d = 'x';
      7 |     let e = "hi";
      8 |     let f = [1, 2, 3];
      9 |     let g = (1, 2.0, 'c');
     10 |     println!("42 → {}", tn(&a));
     11 |     println!("2.5 → {}", tn(&b));
     12 |     println!("true → {}", tn(&c));
     13 |     println!("'x' → {}", tn(&d));
     14 |     println!("\"hi\" → {}", tn(&e));
     15 |     println!("[1,2,3] → {}", tn(&f));
     16 |     println!("(1,2.0,'c') → {}", tn(&g));
     17 | }
  编译:成功
  运行(退出码 0):
    42 → i32
    2.5 → f64
    true → bool
    'x' → char
    "hi" → &str
    [1,2,3] → [i32; 3]
    (1,2.0,'c') → (i32, f64, char)

推断与标注

实测 · 本机执行输出
  源码:
      1 | fn tn<T>(_: &T) -> &'static str { std::any::type_name::<T>() }
      2 | fn takes_u8(_: u8) {}
      3 | fn main() {
      4 |     let a = 5;            // 暂时不知道是什么整数类型
      5 |     takes_u8(a);          // 后面这一行把它定成 u8
      6 |     println!("a: {}", tn(&a));
      7 |     let mut v = Vec::new();
      8 |     v.push(1.5f32);
      9 |     println!("v: {}", tn(&v));
     10 | }
  编译:成功
  运行(退出码 0):
    a: u8
    v: alloc::vec::Vec<f32>
  源码:
      1 | fn main() {
      2 |     let n = "5".parse().unwrap();
      3 |     println!("{}", n);
      4 | }
  编译:失败
    error[E0284]: type annotations needed
     --> <实验目录>/src/u5d.rs:2:9
      |
    2 |     let n = "5".parse().unwrap();
      |         ^       ----- type must be known at this point
      |
      = note: cannot satisfy `<_ as FromStr>::Err == _`
    help: consider giving `n` an explicit type
      |
    2 |     let n: /* Type */ = "5".parse().unwrap();
      |          ++++++++++++
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0284`.

推断是函数内的局部推断:函数签名里的类型必须写出来。

整数溢出

实测 · 本机执行输出
  (1) debug 构建:
  源码:
      1 | fn main() {
      2 |     let n: u8 = std::env::args().count() as u8 + 254;   // 运行时才知道:255
      3 |     println!("n = {n}");
      4 |     println!("checked_add(1)    = {:?}", n.checked_add(1));
      5 |     println!("wrapping_add(1)   = {}", n.wrapping_add(1));
      6 |     println!("saturating_add(1) = {}", n.saturating_add(1));
      7 |     println!("overflowing_add(1)= {:?}", n.overflowing_add(1));
      8 |     let m = n + 1;        // 普通的 +
      9 |     println!("n + 1 = {m}");
     10 | }
  编译:成功
  运行(退出码 101):
    n = 255
    checked_add(1)    = None
    wrapping_add(1)   = 0
    saturating_add(1) = 255
    overflowing_add(1)= (0, true)
    
    thread 'main' (6927394) panicked at <实验目录>/src/u5e.rs:8:13:
    attempt to add with overflow
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
  (2) release 构建 (-O):
  编译:成功
  运行(退出码 0):
    n = 255
    checked_add(1)    = None
    wrapping_add(1)   = 0
    saturating_add(1) = 255
    overflowing_add(1)= (0, true)
    n + 1 = 0

debug 与 release 的这个差别来自 overflow-checks 设置(见Cargo Profile、Debug 与 Release 构建差异)。release 下的回绕是定义好的行为,不是未定义行为。

as 转换

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     println!("300i32 as u8        = {}", 300i32 as u8);
      3 |     println!("-1i32 as u32        = {}", -1i32 as u32);
      4 |     println!("200u8 as i8         = {}", 200u8 as i8);
      5 |     println!("3.99f64 as i32      = {}", 3.99f64 as i32);
      6 |     println!("-3.99f64 as i32     = {}", -3.99f64 as i32);
      7 |     println!("1e20f64 as i32      = {}", 1e20f64 as i32);
      8 |     println!("f64::NAN as i32     = {}", f64::NAN as i32);
      9 |     println!("65u8 as char        = {}", 65u8 as char);
     10 |     println!("'中' as u32         = {}", '中' as u32);
     11 |     println!("true as i32         = {}", true as i32);
     12 | }
  编译:成功
  运行(退出码 0):
    300i32 as u8        = 44
    -1i32 as u32        = 4294967295
    200u8 as i8         = -56
    3.99f64 as i32      = 3
    -3.99f64 as i32     = -3
    1e20f64 as i32      = 2147483647
    f64::NAN as i32     = 0
    65u8 as char        = A
    '中' as u32         = 20013
    true as i32         = 1

分步演示

基本类型
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。目标 aarch64-apple-darwin(64 位),isize/usize 为 8 字节;溢出实验用同一份代码分别以默认(debug)与 -O 编译
    • 没有验证:32 位目标上的 usize、浮点的 NaN 比较与排序、i128 的性能、overflow-checks 在 release 下手动打开的行为、宽度不同的整数混合运算为何必须显式转换
    京ICP备2026067502号-1京公网安备11010502063487号