Rust-Slice类型
另一个没有所有权的数据类型是 Slice。Slice允许你引用集合中一段连续的元素序列,而不是引用整个集合。
我们看看一个编程小习题:写一个函数,该函数接收一个字符串,并返回在该字符串中找到的第一个单词。如果函数在该字符串中并未找到空格,则整个字符串就是一个单词,那应该返回整个字符串。
我们定义函数名 firstWord,有一个参数 &String。因为我们不需要所有权,所以这没有问题。不过应该返回什么呢?我们并没有一个真正获取部分字符串的办法。不过,我们可以返回单词结尾的索引。
fn firstWord(s: &String) -> usize { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if (item == b' ') { return i; } } return s.len(); }
firstWord函数返回String参数的一个字节索引值。因为需要逐个元素的检查String中的值是否为空格,需要用as_bytes()方法将String转化为字节数组;接下来,使用iter()方法在字节数组上创建一个迭代器:
for (i, &item) in bytes.iter().enumerate() {
现在,只需知道iter方法返回集回中的每一个元素,而enumerate包装了iter的结果,将这些元素作为元组的一部分来返回。enumerate返回的元组中,第一个元素是索引,第二个元素是集合的元素的引用。这比我们自已计算索引要方便一些。
因为enumerate方法返回一个元组,我们可以使用模式来解构,就像Rust中其他任何地方所做的一样。所以在for循环中,我们指定了一个模式,其中元组中的i是索引而元组中的 &item 是单个字节。因为我们从 .iter().enumerate() 中获取了集合元素的引用,所以模式中使用了 &。在 for 循环中,我们通过字节的字面值语法来寻找代表空格的字节。如果找到了一个空格,返回它的位置。否则,使用 s.len() 返回字符串的长度。
现在有了一个找到字符串第一个单词结尾索引的方法,不过这有一个问题。我们返回了一个独立的usize,不过它只在 &String 的上下文中才是一个有意义的数字。换句话说,因为它是一个与 String 相分离的值,无法保证将来它仍然有效。
以下我们调用firstWord函数:
let mut s = String::from("hello world"); let word = firstWord(&s);//word的值为5 s.clear();//清空s,使其等于 "" //word在此处的值在这里还是5。 //但没有更多的字符串让我们可以有效地应用数值5。word的值完全是无效。
以上代码存储firstWord函数调用的返回值并接着改变String的内容。这个程序编译时没有任何错误,而且在调用s.clear()之后使用word也不会出错。因为word与s状态完全没有联系,所以word仍然包含值5。可以尝试使用值5来提取变量s的第一个单词,不过这是有bug的,因为在我们将5保存到word之后s的内容已经改变。
我们不得不担心word的索引与s中的数据不再同步,这很麻烦且易出错。如果编写这么一个secondWord函数的话,管理索引这件事将更加容易出问题。如函数名是这样:
fn secondWord(s: &String)->(usize,usize){}
现在我们要跟踪一个开始索引和一个结尾索引,同时有了更多从数据的某个特定状态计算而来的值,但都完全没有与这个状态相关联。现在有三个飘忽不定的不相关变量需要保持同步。
幸运的是,Rust为这个问题提供了一个解决方法:字符串 slice。
字符串slice
字符串slice (string slice)是String中一部分值的引用,它看起来像这样:
let s = String::from("hello world"); let hello = &s[0..5]; let world = &s[6..11];
这类似于引用整个String,不过带有额外的[0..5]部分。它不是对整个String的引用,而是对部分String的引用。
可以使用一个由中括号中的[starting_index .. ending_index]指定的range创建一个slice,其中starting_index是slice的第一个位置,ending_index则是slice最后一个位置的后一个值。
以下示意图表示引用了部分String的字符串slice
所有权、借用和slice这些概念让Rust程序在编译时确保内存安全。Rust语言提供了跟其他系统编程语言相同的方式来控制你使用内存,但拥有数据所有者在离开作用域后自动清除其数据的功能意味着你无须额外编写和调试相关的控制代码。