切片类型
Slice 允许您引用集合中的连续元素序列,而不是整个集合。一个 slice 是一种引用,因此它没有所有权。
这里有一个小编程问题:编写一个函数,它接受一个字符串 words 中用空格分隔,并返回在该字符串中找到的第一个单词。 如果函数在字符串中找不到空格,则整个字符串必须为 一个单词,因此应返回整个字符串。
让我们来看看如何在不使用 slices 来理解 slices 将解决的问题:
fn first_word(s: &String) -> ?
这first_word
函数具有&String
作为参数。我们不想
所有权,所以这很好。但是我们应该返回什么呢?我们真的没有
来谈论字符串的一部分。但是,我们可以返回
单词的结尾,由空格表示。让我们试一试,如示例 4-7 所示。
fn first_word(s: &String) -> usize { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return i; } } s.len() } fn main() {}
first_word
函数将字节索引值返回到String
参数因为我们需要通过String
逐个元素并检查
值是一个空格,我们将String
使用命令的as_bytes
方法。
fn first_word(s: &String) -> usize {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return i;
}
}
s.len()
}
fn main() {}
接下来,我们使用iter
方法:
fn first_word(s: &String) -> usize {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return i;
}
}
s.len()
}
fn main() {}
我们将在第 13 章更详细地讨论迭代器。
现在,请知道iter
是返回集合中每个元素的方法
而那enumerate
将iter
并将每个元素作为
是元组的一部分。从enumerate
是索引,第二个元素是对元素的引用。
这比我们自己计算指数要方便一些。
因为enumerate
方法返回一个元组,我们可以使用 patterns 来
解构该元组。我们将在 Chapter 中详细讨论模式
6. 在for
循环中,我们指定一个具有i
对于元组中的索引,将&item
对于元组中的单个字节。
因为我们从.iter().enumerate()
,我们在 pattern 中使用。&
在for
循环中,我们搜索表示空格的字节
使用 byte literal 语法。如果我们找到一个空格,我们返回位置。
否则,我们使用s.len()
.
fn first_word(s: &String) -> usize {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return i;
}
}
s.len()
}
fn main() {}
我们现在有办法找出
string,但有一个问题。我们将返回一个usize
但它是
仅在&String
.换句话说,
因为它是独立于String
,则无法保证它
将来仍然有效。考虑示例 4-8 中的程序,
使用first_word
示例 4-7 中的 function 来获取。
fn first_word(s: &String) -> usize { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return i; } } s.len() } fn main() { let mut s = String::from("hello world"); let word = first_word(&s); // word will get the value 5 s.clear(); // this empties the String, making it equal to "" // word still has the value 5 here, but there's no more string that // we could meaningfully use the value 5 with. word is now totally invalid! }
first_word
函数,然后将String
内容该程序编译时没有任何错误,如果我们使用word
调用s.clear()
.因为word
未连接到s
完全word
still 包含值5
.我们可以使用该值5
跟
变量s
尝试提取第一个单词,但这将是一个错误
因为s
自从我们保存以来发生了变化5
在word
.
不得不担心word
与s
乏味且容易出错!如果
我们编写一个second_word
功能。它的签名必须如下所示:
fn second_word(s: &String) -> (usize, usize) {
现在,我们正在跟踪起始索引和结束索引,并且还有更多 根据特定状态中的数据计算但未绑定到 那个州。我们有三个不相关的变量围绕着这个需求 保持同步。
幸运的是,Rust 有一个解决这个问题的方法:字符串切片。
字符串切片
字符串 slice 是对String
,它看起来像这样:
fn main() { let s = String::from("hello world"); let hello = &s[0..5]; let world = &s[6..11]; }
而不是对整个String
,hello
是对
部分的String
,在额外的[0..5]
位。我们创建切片
通过指定[starting_index..ending_index]
,
哪里starting_index
是切片中的第一个位置,而ending_index
是
比切片中的最后一个位置多 1。在内部,切片数据
structure 存储切片的起始位置和长度,其中
对应于ending_index
减去starting_index
.因此,在let world = &s[6..11];
,world
将是一个切片,其中包含指向
索引 6 处的字节s
长度值为5
.
图 4-7 在图表中显示了这一点。
图 4-7:引用 a 的一部分的字符串 sliceString
使用 Rust 的..
range 语法,如果要从索引 0 开始,可以丢弃
两个句点之前的值。换句话说,这些是相等的:
#![allow(unused)] fn main() { let s = String::from("hello"); let slice = &s[0..2]; let slice = &s[..2]; }
同样,如果您的 slice 包含String
你
可以删除尾随数字。这意味着它们是相等的:
#![allow(unused)] fn main() { let s = String::from("hello"); let len = s.len(); let slice = &s[3..len]; let slice = &s[3..]; }
您还可以删除这两个值以获取整个字符串的切片。所以这些 相等:
#![allow(unused)] fn main() { let s = String::from("hello"); let len = s.len(); let slice = &s[0..len]; let slice = &s[..]; }
注意:字符串切片范围索引必须出现在有效的 UTF-8 字符处 边界。如果您尝试在 multibyte 字符,则程序将退出并显示错误。目的 在引入字符串切片时,我们只在本节中假设 ASCII;一个 对 UTF-8 处理的更深入讨论在“存储 UTF-8 编码的 Text with Strings“部分。
考虑到所有这些信息,让我们重写first_word
要返回
片。表示 “string slice” 的类型写为&str
:
fn first_word(s: &String) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] } fn main() {}
我们得到单词末尾的索引的方式与示例 4-7 中相同,由 查找 Space 的第一个匹配项。当我们找到一个空格时,我们返回一个 string 切片,使用字符串的开头和空格的索引作为 起始索引和结束索引。
现在,当我们调用first_word
,我们得到一个与
基础数据。该值由对
切片和切片中的元素数。
返回 slice 也适用于second_word
功能:
fn second_word(s: &String) -> &str {
我们现在有一个简单的 API,它更难搞砸,因为
编译器将确保对String
保持有效。记得
示例 4-8 中程序中的 bug,当我们获取索引到
第一个单词,但随后清除了字符串,所以我们的索引无效?该代码是
逻辑上不正确,但没有立即显示任何错误。问题会
如果我们一直尝试使用第一个单词索引和 emptyTed,则稍后显示
字符串。切片使这个错误变得不可能,并让我们知道我们有一个问题
我们的代码要快得多。使用first_word
会抛出一个
编译时错误:
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);
s.clear(); // error!
println!("the first word is: {word}");
}
这是编译器错误:
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:18:5
|
16 | let word = first_word(&s);
| -- immutable borrow occurs here
17 |
18 | s.clear(); // error!
| ^^^^^^^^^ mutable borrow occurs here
19 |
20 | println!("the first word is: {word}");
| ------ immutable borrow later used here
For more information about this error, try `rustc --explain E0502`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error
回想一下借用规则,如果我们有一个对
something,我们也不能采用可变引用。因为clear
需要
截断String
,它需要获取一个可变引用。这println!
调用clear
使用word
,因此 immutable
此时 reference 必须仍处于 active 状态。Rust 不允许 mutable
引用clear
和word
从现有的
同时,编译失败。Rust 不仅使我们的 API 更易于使用,
但它也消除了编译时的一整类错误!
字符串文本作为切片
回想一下,我们讨论了存储在二进制文件中的字符串 Literals。现在 了解了 slices,我们可以正确理解字符串字面量:
#![allow(unused)] fn main() { let s = "Hello, world!"; }
的类型s
这是&str
:它是一个指向
二进制。这也是字符串 Literals 不可变的原因;&str
是一个
immutable 引用。
字符串切片作为参数
知道你可以获取 Literals 的切片和String
价值观引导我们
再改进first_word
,这就是它的签名:
fn first_word(s: &String) -> &str {
更有经验的 Rustacean 会写示例 4-9 中所示的签名
相反,因为它允许我们在两者上使用相同的函数&String
值
和&str
值。
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let my_string = String::from("hello world");
// `first_word` works on slices of `String`s, whether partial or whole
let word = first_word(&my_string[0..6]);
let word = first_word(&my_string[..]);
// `first_word` also works on references to `String`s, which are equivalent
// to whole slices of `String`s
let word = first_word(&my_string);
let my_string_literal = "hello world";
// `first_word` works on slices of string literals, whether partial or whole
let word = first_word(&my_string_literal[0..6]);
let word = first_word(&my_string_literal[..]);
// Because string literals *are* string slices already,
// this works too, without the slice syntax!
let word = first_word(my_string_literal);
}
first_word
函数,方法是使用字符串切片作为s
参数如果我们有一个字符串 slice,我们可以直接传递它。如果我们有一个String
我们
可以将String
或引用String
.这
灵活性利用了 Deref 强制转换,我们将在“使用 Functions 和
方法“部分。
定义一个函数来获取字符串 slice 而不是对String
使我们的 API 更通用、更有用,而不会丢失任何功能:
fn first_word(s: &str) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] } fn main() { let my_string = String::from("hello world"); // `first_word` works on slices of `String`s, whether partial or whole let word = first_word(&my_string[0..6]); let word = first_word(&my_string[..]); // `first_word` also works on references to `String`s, which are equivalent // to whole slices of `String`s let word = first_word(&my_string); let my_string_literal = "hello world"; // `first_word` works on slices of string literals, whether partial or whole let word = first_word(&my_string_literal[0..6]); let word = first_word(&my_string_literal[..]); // Because string literals *are* string slices already, // this works too, without the slice syntax! let word = first_word(my_string_literal); }
其他切片
正如您可能想象的那样,字符串切片是特定于字符串的。但是有一个 更通用的切片类型也是如此。考虑这个数组:
#![allow(unused)] fn main() { let a = [1, 2, 3, 4, 5]; }
正如我们可能想要引用字符串的一部分一样,我们可能想要引用 数组的一部分。我们会这样做:
#![allow(unused)] fn main() { let a = [1, 2, 3, 4, 5]; let slice = &a[1..3]; assert_eq!(slice, &[2, 3]); }
此切片的类型为&[i32]
.它的工作方式与字符串切片相同,通过
存储对第一个元素的引用和长度。您将使用这种
slice 用于各种其他集合。我们将在
细节。
总结
所有权、借用和切片的概念确保了 Rust 中的内存安全 程序。Rust 语言让你控制你的内存 用法与其他 Systems 编程语言相同,但具有 数据所有者会在所有者超出范围时自动清理该数据 意味着您不必编写和调试额外的代码来获得此控制权。
所有权会影响 Rust 的许多其他部分的工作方式,因此我们将讨论
这些概念在本书的其余部分得到了进一步的阐述。让我们继续
第 5 章,了解如何将数据分组到struct
.
本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准