Rust 切片
Source: Dev.to

我大多数关于 Rust 的写作都是从 JavaScript/TypeScript 开发者的视角出发的。直观上,当你通过另一种语言学习新语言时,往往会在两者之间建立概念映射。例如,Rust 中的 struct 可以映射到 TypeScript 中的 interface 或 type。这样做可以让你更容易在新语言中吸收知识,因为概念突然变得熟悉。
但这种学习模式会带来一个微妙的问题:一旦你习惯了不断进行概念映射,就会更难真正掌握在新语言中没有直接对应的概念。这正是我在 Rust 中遇到切片(slices)时的障碍。
本文假设读者已经具备 Rust 基础概念的理解,如引用、借用和所有权。
常见的 Rust 模式
在 Rust 中,当编写不需要修改参数的函数时,通常的最佳实践是使用不可变(只读)引用。为了说明这一点,我们来编写一个接受全名并返回仅包含名字的函数。
fn get_first_name(full_name: &str) -> &str {
full_name
.split_whitespace()
.next()
.unwrap_or("")
}
这段代码在做什么?
- 该函数借用一个字符串切片(
&str);它并不获取所有权。 - 它按空白字符将字符串拆分成若干单词。
- 它取出第一个单词(如果有的话)。
- 如果没有单词,则返回
""。 - 它返回的是原始字符串的切片,而不是一个新的
String。
让我们拆开来看看 😌😉
“返回切片”到底是什么意思?
该函数并没有创建一个新字符串,而是仅仅指向现有字符串的一部分。
如果我们的原始字符串是 "Lex Luthor",一个切片可以指向:
"Lex""Luthor""thor""ex Lu"- 或者甚至是
"Lex Luthor"(整个字符串)
所有这些都只是对同一底层字符串数据的视图。
JavaScript 心智模型(这有帮助)
如果你来自 JavaScript,脑中可能会直接想到 子串。子串是另一个字符串的一部分,其起始和结束索引都位于原字符串的范围内。如果起始索引是 0,结束索引是 string.length,则该子串代表整个原字符串。
这在概念上也类似于 Rust 中的切片——但有一个重要区别:
切片是引用,而不是新值。 这一点至关重要。
为什么会有切片
如果你已经拥有一个字符串的引用,并且想引用该字符串的一部分,你可以分配一个新字符串并将数据复制进去。这将涉及:
- 分配新内存
- 复制字节
- 做一些你其实不需要的额外工作
Rust 说:为什么不直接引用原始数据的一部分呢? 没有复制,没有新分配,一切仍然快速且明确。这正是切片存在的原因。
切片不仅适用于字符串
切片同样适用于数组,语法看起来非常相似。
对字符串
let original_value = String::from("Hello world");
let slice = &original_value[1..7];
slice 是一个指向 original_value 中 "ello w" 的 &str。
对数组
let ages = [13, 21, 37, 4, 55];
let slice = &ages[2..4];
slice 是对数组一部分的引用:[37, 4]。
切片的形式为 &original_value[start_index..end_index],其中 start_index 为包含,end_index 为不包含。你也可以省略一个或两个索引:
&value[..]→ 整个值&value[..3]→ 从开头到索引 2(不含 3)&value[3..]→ 从索引 3 到结尾
最终感想
Slices 一开始让我感到陌生,因为我一直试图把它们强行套用到熟悉的 JS 概念中。等我不再把它们当作“新值”来思考,而是把它们视为对已有数据的引用时,一切就明白了。
如果你想深入了解,官方的 Rust 书籍对 slices 进行了精彩且更详细的解释,并提供了一个强有力的示例,说明为什么应该使用 slices:
👉