slice 类型
slice
类型允许引用集合中一段连续的元素序列,而不用引用整个集合 。
slice
是一类引用,所以没有所有权。
fn main () {
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 ()
}
println! ( "{}" , first_word (& String :: from ( "hello world" )));
}
在上面代码,实现了一个函数,该函数接收一个用空格分隔单词的字符串,并返回在该字符串中找到的第一个单词。如果函数在该字符串中并未找到空格,则整个字符串就是一个单词,所以应该返回整个字符串。
as_bytes()
函数将 String
转化为字节数组,再通过 iter
方法在字节数组上创建一个迭代器。iter
方法会返回集合中的每一个元素,而 enumerate
方法包装了 iter
的结果,将这些元素作为元组的一部分来返回。enumerate
返回的元组中,第一个元素是索引,第二个元素是集合中元素的引用。在 for
循环中,通过字节的字面值语法来寻找代表空格的字节。
fn main () {
let mut s = String :: from ( "hello world" );
let word = first_word (& s ); // word 的值为 5
s . clear (); // 这清空了字符串,使其等于 ""
// word 在此处的值仍然是 5,
// 但是没有更多的字符串让我们可以有效地应用数值 5。word 的值现在完全无效!
}
以上代码会造成 word
索引与 s
中的数据不同步,这会造成问题。
如何解决这个问题呢?字符串 slice
可以解决。
字符串 slice
字符串 slice(string slice)是 String
中一部分的值的引用
fn main () {
let s = String :: from ( "hello world" );
let hello = & s [ 0 .. 5 ];
let world = & s [ 6 .. 11 ];
println! ( "{} {}" , hello , world )
}
不同于整个 String
的引用,hello
和 world
都是一部分 String
的引用,可以使用一个由中括号的 [start_index, end_index]
指定的 range
创建一个 slice
。
对于字符串的 range
语法来说:
如果想要从索引 0
开始,可以不写两个点号前面的值
let s = & s [ 0 .. 3 ];
// 等价于
let s = & s [.. 3 ];
如果 slice
包含 String
得最后一个字节,也可以不写两个点号后面的值
let s = String :: from ( "hello" );
let len = s . len ();
let slice = & s [ 3 .. len ];
// 等价于
let slice = & s [ 3 ..];
也可以同时舍弃前后两个值,来获取整个字符串的 slice
let s = String :: from ( "hello" );
let len = s . len ();
let slice = & s [ 0 .. len ];
// 等价于
let slice = & s [..];
字符串 slice
的类型声明是 &str
fn main () {
fn first_world ( s : & String ) -> & str {
let bytes = s . as_bytes ();
for ( i , & item ) in bytes . iter (). enumerate () {
if item == b' ' {
return & s [ 0 .. i ];
}
}
& s [..]
}
println! ( "{}" , first_world (& String :: from ( "Hello World" )));
}
这时当调用了 first_world
函数后,它会返回一个与底层数据相关联的值,这个值由一个 slice
开始位置的引用和 slice
中元素的数量组成。
再回到最开始的问题,如果这时候将 first_world
方法使用 slice
类型会怎么样?
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 ); // 值为 slice 类型
s . clear (); // 错误!
println! ( "the first word is: {}" , word );
}
上方代码进行编译后,会出现如下错误:
|
113 | let word = first_word (& s );
| -- immutable borrow occurs here
114 |
115 | s . clear (); // 错误!
| ^^^^^^^^^ mutable borrow occurs here
116 |
117 | println! ( "the first word is: {}" , word );
| ---- immutable borrow later used here
word
变量是一个不可变引用,当调用 clear
方法后,变量 s
尝试获取一个可变引用。回顾一下借用原则:不可变引用不能与可变引用共存,也就是说一个值有了不可变引用,就不能再获取一个可变引用。因此导致编译失败!
字符串字面值就是 slice
let s = "Hello World" ;
这里的变量 s
就是 slice
类型,即 &str
。它是一个指向二进制程序特定位置的 slice
类型,这就是为什么字符串字面值是不可变的,因为 &str
是一个不可变引用。
字符串 slice
作为参数
fn main () {
fn first_world ( s : & str ) -> & str {
...
}
}
如果参数是一个字符串 slice
,则可以直接传递一个字符串 slice
,也可以传递整个 String
的 slice
或对 String
的引用。这样使得 API 更加通用且不会丢失任何功能。