在 Rust 中,类型系统不仅用于约束错误,更是表达意图的工具。掌握整数与浮点数、布尔与字符、数组与切片、元组与字符串等基础类型,是理解所有权、生命周期、泛型与并发等进阶主题的前提。

在 Rust 里,整数类型可以分为有符号和无符号两大类。有符号整数以 i 开头,比如 i8、i16、i32、i64、i128,能表示正数、负数和零;
无符号整数以 u 开头,比如 u8、u16、u32、u64、u128,只表示零和正数。此外,还有 isize 和 usize,它们的位宽会根据我们运行的平台自动调整,适合用来表示内存地址或集合的长度。
浮点数类型有 f32 和 f64,其中 f64 是默认类型,精度更高。我们在写数值字面量时,可以用不同的进制前缀,比如 0b 表示二进制,0o 表示八进制,0x 表示十六进制。
为了让长数字更易读,还可以在数字中间加下划线分隔,比如 1_000_000 代表一百万。
|fn main() { let dec = 1_234u32; let hex = 0xff_u8; let bin = 0b1010_1010u8; let oct = 0o755u16; println!("{dec} {hex} {bin} {oct}"); }
|1234 255 170 493
在 Rust 里,如果我们在调试模式(debug)下让整数发生溢出,比如让一个 u8 类型的变量加到 256 以上,程序会直接 panic,帮助我们及时发现 bug。 而在发布模式(release)下,相同的溢出操作不会报错,而是采用二进制回绕的方式,比如 255 加 1 会变成 0。
针对不同的业务需求,标准库还为我们准备了多种带有不同溢出语义的方法,比如 checked_add 会在溢出时返回 None,saturating_add
会在溢出时返回类型能表示的最大值,wrapping_add 则直接回绕到零点重新计数,overflowing_add 不仅返回结果,还告诉我们是否发生了溢出。
这样一来,我们可以根据实际场景,明确地选择最合适的行为:
|fn main() { let x: u8 = 250; println!("{:?}", x.checked_add(10)); // None println!("{}", x.saturating_add(10)); // 255 println!("{}", x.wrapping_add(10)); // 4 println!(
在 Rust 里,浮点数类型(如 f32 和 f64)采用 IEEE 754 标准进行存储和运算,这意味着它们不仅能表示普通的实数,还能表示正无穷大、负无穷大以及特殊的“非数”(NaN,Not a Number)。
当我们在程序中遇到 NaN 时,无论用等于还是不等于去比较,结果都会是 false,也就是说 NaN != NaN 依然成立。
这种特性会影响排序和查找等操作,所以我们在处理包含 NaN 的浮点数据时要格外小心。
此外,由于浮点数的精度有限,直接用 == 判断两个浮点数是否相等往往会出现意料之外的结果。
我们通常会采用“容差比较”的方式,也就是判断两个数的差值是否在一个很小的范围内,这样才能更可靠地比较浮点数是否“足够接近”。
|fn close(a: f64, b: f64, eps: f64) -> bool { (a - b).abs() < eps } fn main() { let x = 0.1f64 + 0.2f64; println!("x = {x}"); println!("close? {}", close(x,
|x = 0.30000000000000004 close? true true true
在 Rust 语言中,整数类型不仅可以进行常见的加法、减法、乘法、除法和取余等数值运算,还可以直接操作每一位的二进制值,比如按位与、按位或、按位异或、按位取反,以及左移和右移等操作。 需要特别注意的是,进行移位操作时,右侧的移位数值必须在当前整数类型的位宽范围之内,比如 u8 类型最多只能移 0 到 7 位,超出范围会导致未定义行为。 Rust 编译器会在编译阶段或者运行时对移位的合法性进行检查,帮助我们及时发现潜在的错误,保证程序的健壮性和安全性。
|fn main() { let a = 0b1010_0001u8; // 161 let b = 0b0110_1100u8; // 108 println!("and={} or={} xor={} shl={} shr={}", a & b, a | b, a ^ b, a << 2, b >> 3); }
在 Rust 里,如果变量的类型无法通过上下文自动推断出来,我们就需要在字面量后面加上类型后缀,比如写成 42u32 或 3.14f64,这样编译器才能明确知道具体类型。
对于那些需要暴露给外部使用的接口,或者我们希望代码更清晰易懂的场合,也建议主动标注类型,这样别人阅读代码时能一目了然,减少歧义和误解。
|fn area(width: u32, height: u32) -> u32 { width * height } fn main() { let w = 16u32; // 后缀明确类型 let h: u32 = 9; // 注解明确类型 println!("{}", area(w, h)); }
在 Rust 语言中,条件判断表达式必须严格返回布尔类型 bool,而不像某些其他语言那样允许用整数、指针或其他类型进行“非零即真”的隐式转换。
这样设计可以从根本上避免歧义和潜在的逻辑错误,让我们的代码更加清晰和安全。例如,不能直接把一个数字用作 if 条件,必须显式写成比较表达式,如 x != 0,否则编译器会报错。
|fn main() { let pass = 87 >= 60; let res = if pass { "pass" } else { "fail" }; println!("{res}"); }
在 Rust 语言中,char 类型用于表示单个 Unicode 字符,它是一个 4 字节的标量值,可以容纳任何有效的 Unicode 字符。
与 C/C++ 不同,Rust 中的 char 类型不能直接用作条件判断,必须显式转换为布尔值。
|fn main() { let zh: char = '学'; let emoji: char = '🦀'; println!("{} {} {}", zh, zh as u32, emoji); }
|学 23398 🦀
str 与 String在 Rust 里,str 是最基础的字符串类型,通常我们会以 &str 的形式来使用它,比如字符串字面量或者对字符串的只读借用。String 则是一个拥有所有权的字符串类型,它会在堆上分配内存,支持动态增长和内容修改。我们可以很方便地把 &String 当作 &str 来用,因为它们之间可以自动转换;但如果想把 &str 变成 String,就需要分配新的堆内存,把内容复制过去。
|fn greet(name: &str) -> String { format!("你好,{name}!") } fn main() { let owned = String::from("Rust"); let borrowed: &str = &owned; let msg = greet(borrowed); println!("{msg}"
|你好,Rust!
在 Rust 里,字符串的切片操作其实是按照字节来定位的。我们在取字符串的某一部分时,必须保证切片的起始和结束位置都正好落在 UTF-8 字符的边界上。 只要有一个位置落在字符的中间,程序就会直接 panic 报错。所以,处理多字节字符(比如中文或 emoji)时,切片范围一定要小心,不能随意指定字节下标。
|fn main() { let s = "你好Rust"; // UTF-8 中文占多字节 let hello = &s[0..6]; // “你”(3字节) + “好”(3字节) println!("{hello}"); }
在 Rust 里,我们经常需要把多个字符串合成一个,或者在已有字符串中插入内容,还会用到格式化来生成带变量的文本。
拼接字符串时,最常见的方式是用 push_str 方法把一个字符串片段加到已有的 String 后面。
如果要插入单个字符,可以用 push 方法。对于更复杂的场景,比如要把变量嵌入到字符串里,format! 宏就非常方便,它能像模板一样,把变量值填充到指定位置,生成一个新的 String。
举个例子,我们想把“欢迎”这个词和一个名字拼成一句完整的问候语,可以先用 String::from 创建一个可变字符串,然后用 push_str 把名字加上去。
假如还想在名字后面加个感叹号,可以再用 push 插入字符。如果我们有多个变量,比如年级和班级,也可以用 format! 直接生成类似“张三同学,欢迎来到高一三班!”这样的句子。
这些操作都不会影响原有的字符串切片 &str,因为只有 String 类型才支持内容的修改和扩展。每次用 format! 都会生成一个新的字符串,不会改变原有变量的值。这样既保证了安全,也让我们在处理文本时更加灵活。
|fn main() { let mut s = String::from("Rust"); s.push(' '); s.push_str("1.0"); let t = format!("{s} + {}", "hello"); println!("{t}"); }
|Rust 1.0 + hello
数组就是长度固定、类型一致的一组数据,比如 [i32; 4] 表示 4 个整型元素,长度在编译时就定死了。
切片(&[T] 或 &mut [T])可以理解为“数组的一部分视图”,它能指向数组里连续的一段数据,长度可以变化。
我们写函数时,通常用切片当参数,这样不管传整个数组还是其中一段都行,代码更灵活。
|fn sum(slice: &[i32]) -> i32 { slice.iter().sum() } fn main() { let arr: [i32; 4] = [1, 2, 3, 4]; let s1 = &arr[..
|4 3 9
我们可以用切片自带的 windows、chunks、chunks_exact 这些方法,把一段数据按固定长度一块一块地分开来看,非常适合需要分组处理的场景。
|fn main() { let a = [1,2,3,4,5,6]; for w in a.windows(3) { print!("{:?} ", w); } println!(); for c in a.chunks(2) { print!
|[1, 2, 3] [2, 3, 4] [3, 4, 5] [4, 5, 6] [1, 2] [3, 4] [5, 6]
元组(tuple)可以把多个不同类型的数据按顺序组合在一起,形成一个整体。我们可以把它想象成一个小包裹,里面可以装下不同种类的东西,比如数字、字符串、布尔值等。 元组最常见的用途有两个:一是当我们需要从函数里一次性返回多个不同类型的值时,用元组打包返回最方便; 二是临时把一些相关但类型不同的数据组合在一起,便于后续处理。比如说,我们想统计一组数据的长度、总和和最大值,这三者类型不一样,就可以用元组一起返回。
|fn stats(values: &[i32]) -> (usize, i32, i32) { let len = values.len(); let sum: i32 = values.iter().sum(); let max = *values.iter().
as、From/TryFrom、Into/TryInto在 Rust 里,类型转换有几种常见方式,各有适用场景和注意事项。我们可以用 as 关键字进行强制类型转换,这种方式非常直接,但如果目标类型无法完整表示原值,可能会发生数据截断或溢出,导致信息丢失。
比如把一个较大的整数强转成较小的类型时,超出的部分会被丢弃。
如果我们希望类型转换更安全,可以用 From 和 Into 这两个 trait。它们要求转换在逻辑上是可靠且不会失败的,比如从 u8 转成 u16,因为目标类型能完全容纳原值。这种转换在编译期就能保证安全。
而当转换有可能失败,比如把一个大整数转成小整数,或者把字符串解析成数字时,我们可以用 TryFrom 和 TryInto。这两个 trait 会返回一个 Result,让我们有机会处理失败的情况,避免程序崩溃。
这样,我们可以根据实际情况决定如何应对转换失败,比如给出提示或者使用默认值。
总之,as 适合简单、明确不会出错的场景;From/Into 用于保证安全的自动转换;TryFrom/TryInto 则适合那些有失败风险、需要显式处理错误的类型转换。
|use std::convert::{TryFrom, TryInto}; fn main() { let big: u16 = 300; let small = big as u8; // 截断为 44 println!("{small}"); let ok: u8 = 200u16.try_into()
|44 200 42
9. 摄氏/华氏温度转换练习
编写一个温度转换程序,要求:
celsius_to_fahrenheit(摄氏转华氏)和fahrenheit_to_celsius(华氏转摄氏)|// 摄氏转华氏 fn celsius_to_fahrenheit(celsius: f64) -> f64 { celsius * 9.0 / 5.0 + 32.0 } // 华氏转摄氏 fn fahrenheit_to_celsius(fahrenheit: f64) -> f64 { (fahrenheit - 32.0) * 5.0 / 9.0 } fn main
10. 切片、迭代、Option与Result协作练习
编写一个程序,要求:
|use std::num::Wrapping; // 计算切片元素的和,处理空切片和溢出 fn safe_sum(numbers: &[i32]) -> Result<Option<i32>, &'static str> { // 处理空切片:返回None if numbers.is_empty() { return Ok(None); }
|=== 摄氏转华氏 === 0.00°C = 32.00°F 25.00°C = 77.00°F 37.00°C = 98.60°F 100.00°C = 212.00°F === 华氏转摄氏 === 32.00°F = 0.00°C 77.00°F = 25.00°F 98.60°F = 37.00°C 212.00°F = 100.00°C === 验证转换准确性 === 原始: 25.00°C 转华氏: 77.00°F 转回摄氏: 25.00°C 误差: 0.000000°C
说明:
{:.2}保留两位小数|数组 [1, 2, 3, 4, 5] 的和: 15 数组 [] 为空 数组 [2147483647, 1] 计算时发生: 计算溢出:和超出了i32的最大值 === 使用迭代器版本 === 数组 [10, 20, 30, 40] 的和: 100
说明:
&[i32]作为参数,可以接受数组、向量或切片iter()和for循环,或使用try_fold等迭代器方法Ok(None),非空返回Ok(Some(sum))checked_add检查溢出,溢出时返回Errmatch表达式处理所有可能的情况