在 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
429. 摄氏/华氏温度转换练习
编写一个温度转换程序,要求:
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表达式处理所有可能的情况