写点什么

Rust 从 0 到 1- 所有权 - 切片类型

用户头像
关注
发布于: 2021 年 04 月 06 日
Rust从0到1-所有权-切片类型

切片(Slices)允许你引用集合中一段连续的元素序列,而不用引用整个集合。

我们从一个例子说起,假设有一个函数,该函数返回给定字符串中找到的第一个单词。如果函数在该字符串中并未找到空格,则整个字符串就是一个单词,返回整个字符串。参考下面的例子:

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()}
复制代码

first_word 函数有一个参数 &String。我们不需要所有权,所以使用了引用类型。在 for 循环中,我们寻找代表空格的字节,如果找到了,就返回它的位置索引,否则,使用 s.len() 返回字符串的长度。即,函数返回第一个单词结尾的索引。不过这其中存在一个潜在的问题,我们返回了一个独立的 usize 类型数据,但是它只在字符串存在的上下文中才是一个有意义的数字。换句话说,因为它是一个与字符串相分离的值,无法保证将来它仍然有效。参考下面的场景:

fn main() {    let mut s = String::from("hello world");    let word = first_word(&s); // 返回第一个单词结尾索引 5
s.clear(); // 清空字符串变量 s ,即 s 变为 "" // word 在此处的值仍然是 5,但是无法再使用这个值获得第一个单词 println!("the first word is: {},s is {}", word, s);}
复制代码

这个程序编译时没有任何错误,而且在调用 s.clear() 之后使用 word 也不会出错。但是某些场景下,假设我们需要通过第一个单词的索引 word 来从 s 中取出数据,如果这时候 s 的值发生了改变,就可能会造成错误!因此,需要我们始终记得这个关系,这个既麻烦又容易出错。Rust 为这个问题提供了一个解决方法:字符串切片(String Slices)。

字符串切片

字符串切片(String Slices)是字符串中一部分值的引用,参考下面的例子: 

let s = String::from("hello world");
let hello = &s[0..5];let world = &s[6..11];
复制代码

切片引用和一般的引用类似,只不过只引用整个字符串的一部分,譬如 [0..5] 部分。可以使用 [starting_index..ending_index] 指定的范围创建一个切片,其中 starting_index 是切片的起始位置,ending_index 则是切片最后一个位置+1(注意,整个字符串的初始位置是从 0 开始)。切片的数据结构存储了切片 的开始位置和长度,长度等于 ending_index 减去 starting_index 的值。所以对于 let world = &s[6..11]; 的切片引用,world 包含了指向 第 6 个字节(从 0 开始)的指针和长度值 5。参考下图(来自官网):

另外,关于 Rust 的 .. 语法,如果是从整个字符串的第一个位置(0)开始,那么 0 可以省略不写。同样,如果是直到整个字符串结束,那么结束位置也可以省略不写。如果开始位置和结束位置都不写呢?那就是截取整个字符串(似乎没啥意义,后面讲到切片参数的时候会讲,可以统一参数类型)。参考下面的例子:

let s = String::from("hello");
let slice = &s[0..2];let slice = &s[..2];
let len = s.len();
let slice = &s[3..len];let slice = &s[3..];
let slice = &s[0..len];let slice = &s[..];
复制代码

关于切片还有一点需要注意,切片切的是字节(byte)不是字符,上面的例子由于都是 ASCII 字符,只占用一个字节,不会报错,大家可以试试换成中文看看。如果切到了字符(UTF-8)的中间位置,那么 Rust 程序就会报错退出(UTF-8 文本处理的问题后面会介绍)。

现在我们使用切片重新实现函数 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[..]}
复制代码

编译器会确保指向 的引用持续有效。因此前面的例子中在调用 first_word 后又使用 s.clear()  改变了字符串,Rust 就抛出一个编译时错误(如果后面没有再使用 word ,那么 word 的作用域结束,也不会报错)。

字符串文本就是切片

我们前面有说过通过将字符串文本直接赋值给变量的方式声明的变量,其值不可变,参考下面的例子:

let s = "Hello, world!";
复制代码

这里 的类型就是 &str:它是一个指向程序特定位置的切片。因此是不可变的;因为 &str 是不可变引用。

字符串切片作为参数

字符串切片(类型 &str)也可以作为函数的参数,这样不管是 String 类型还是 String Slices 类型我们都可以作为参数方便的传递给函数,参考下面的例子:

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"); let _word = first_word(&my_string[..]);
let my_string_literal = "hello world"; let _word = first_word(&my_string_literal[..]); // 因为 my_string_literal 本来就是&str类型,所以可以直接作为参数 let _word = first_word(my_string_literal);}
复制代码

其他类型的切片

我们来看下数组,就跟我们想要获取字符串的一部分那样,我们也会想要引用数组的一部分。参考下面的例子:

let a = [1, 2, 3, 4, 5];let slice = &a[1..3];
复制代码

这个切片的类型是 &[i32]。它跟字符串切片的工作方式一样,通过存储第一个集合元素的引用和一个长度,你可以对其他所有集合使用这类切片。后面会详细讨论这些集合。

总结

所有权、借用和切片这些概念让 Rust 在编译时确保内存安全。Rust 可以像其他编程语言一样对内存的使用进行控制,但拥有数据的所有者在离开作用域后自动清除其数据的功能意味着你无须担心内存在释放、共享等方面的安全问题。

所有权系统对 Rust 中很多其他部分都有影响,因此,后面介绍其他内容的时候,都会提到相关的概念。敬请期待;)。

发布于: 2021 年 04 月 06 日阅读数: 22
用户头像

关注

公众号"山 顽石"欢迎大家关注;) 2021.03.23 加入

IT老兵,关注Rust、Java、前端、架构、大数据、AI、算法等;平时喜欢美食、旅游、宠物、健身、篮球、乒乓球等。希望能和大家交流分享。

评论

发布
暂无评论
Rust从0到1-所有权-切片类型