基本类型看起来是最无聊的一章,但很多真实的坑藏在这里:i32 为什么是默认整数、类型推断什么时候放弃、同一份代码在 debug 和 release 下对溢出的反应为什么不同、as 转换到底会丢什么。这篇每条都用真实的编译与运行结果回答。
编译:成功
运行(退出码 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 = 0debug 与 release 的这个差别来自 overflow-checks 设置(见Cargo Profile、Debug 与 Release 构建差异)。release 下的回绕是定义好的行为,不是未定义行为。
源码:
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