17.2 避免未检查的索引 🟢
在 Rust 中,如果索引超出范围或键不存在,对 Vec 或 HashMap 等集合使用索引运算符 ([]) 将导致程序发生 Panic。这是 Rust 程序中常见的崩溃来源。
1. [] 的危险性
虽然 [] 很方便,但它假设索引或键始终有效。如果这个假设错误,你的程序就会崩溃。
fn main() {
let v = vec![1, 2, 3];
// 不良实践:这会发生 Panic,因为索引 10 超出了范围
let x = v[10];
}
2. 使用 .get() 代替
.get() 方法返回一个 Option<&T>,允许你在不崩溃的情况下处理索引或键缺失的情况。
fn main() {
let v = vec![1, 2, 3];
// 良好实践:优雅地处理缺失的索引
match v.get(10) {
Some(x) => println!("值为:{}", x),
None => println!("索引超出范围!"),
}
// 或者使用 unwrap_or 提供默认值
let x = v.get(10).unwrap_or(&0);
}
3. 使用迭代器代替索引
通常,你可以通过使用迭代器完全避免索引。这不仅更安全,而且通常更高效、更地道 (idiomatic)。
fn main() {
let v = vec![1, 2, 3, 4, 5];
// 不良实践:手动索引很容易出错
for i in 0..v.len() {
println!("{}", v[i]);
}
// 良好实践:使用迭代器
for x in &v {
println!("{}", x);
}
}
4. 边界检查与切片 (Slicing)
如果你需要处理集合的一个子部分,请使用 .get(start..end) 进行切片。这也会返回一个 Option,确保你不会意外地越过边界。
fn main() {
let v = vec![1, 2, 3, 4, 5];
if let Some(sub_slice) = v.get(1..3) {
println!("子切片:{:?}", sub_slice); // [2, 3]
}
}
对于 C/C++ 开发者的总结
- 在 C/C++ 中:对
std::vector使用v[i]不执行边界检查,如果索引超出了范围,会导致未定义行为(读取/写入任意内存)。你可能会使用v.at(i),它会抛出异常,但这种情况不太常见。 - 在 Rust 中:
v[i]始终执行边界检查并在失败时发生 Panic。这可以防止内存安全问题,但仍可能导致你的程序退出。使用.get()是 Rust 处理潜在“越界”访问的安全且显式的地道方式。