乐趣区

关于rust:rust学习所属权之Slice

还有一种不领有所属权的数据类型为:slice。

slice 使咱们能够援用汇合中元素的间断序列而不是整个汇合。

一个小的编程问题:编写一个函数,该函数须要一个字符串并返回在该字符串中找到的第一个单词。如果函数在字符串中找不到空格,则整个字符串必须是一个单词,因而应返回整个字符串。

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()}

在上例中:
1、因为咱们须要一一查看 String 元素,并查看值是否为空格,所以咱们将应用 as_bytes 办法将 String 转换为字节数组:let bytes = s.as_bytes();
2、咱们应用 iter 办法在字节数组上创立一个迭代器:for (i, &item) in bytes.iter().enumerate() {

3、在 for 循环内,咱们应用字节文字语法搜寻示意空格的字节。如果找到空格,则返回地位。否则,咱们应用 s.len()返回字符串的长度:

    if item == b' ' {return i;}
 }
 
 s.len()

4、当初,咱们能够找到字符串中第一个单词结尾的索引,但这是有问题的,咱们将本人返回一个 usize,然而在 &String 的上下文中,这只是一个有意义的数字。换句话说,因为它是与 String 离开的值,因而无奈保障它在未来依然无效,所以将其革除:

fn main() {let mut s = String::from("hello world");

    let word = first_word(&s); // word 将要获取到的值为 5

    s.clear(); // 这将清空字符串,使其等于 ""

    // word 依然在这里具备值 5,然而没有更多的字符串可用于有意义地应用值 5. 这个词当初齐全有效!}

该程序编译时没有任何谬误,如果在调用 s.clear()之后应用 word,也能够这样做。因为 word 基本没有连贯到 s 的状态,所以 word 依然蕴含值 5。咱们能够将值 5 与变量 s 一起应用以尝试提取第一个单词,但这将是一个 bug,因为自从咱们保留了 5 到 word 中之后,s 产生了变动。

不用放心 word 中的索引与 s 中的数据不同步,这既繁琐又容易出错!如果咱们编写 second_word 函数,则治理这些索引的难度更大。其签名必须如下所示:
fn second_word(s: &String) -> (usize, usize) {

当初,咱们正在跟踪起始索引和完结索引,并且咱们有更多的值是依据特定状态下的数据计算得出的,但与该状态齐全无关。当初,咱们有三个不相干的变量须要放弃同步。

侥幸的是,Rust 解决了这个问题:字符串切片。

String Slices

字符串切片是对字符串一部分的援用,它看起来像这样:

fn main() {let s = String::from("hello world");

    let hello = &s[0..5];
    let world = &s[6..11];
}

通过指定[starting_index..ending_index],咱们能够应用方括号内的范畴来创立切片,其中,starting_index 是切片中的第一个地位,而 ending_index 是一个能够比切片中的最初一个地位大的数。在外部,切片数据结构存储切片的起始地位和长度,它对应于 ending_index 减去 starting_index。因而,在 let world =&s [6..11]; 的状况下,world 将是一个切片,其中蕴含指向 s 的第 7 个字节(从 1 开始)的指针,其长度值为 5。

应用 Rust 的.. 范畴语法,如果想从第一个索引(零)开始,则能够在两个句点之前删除该值。换句话说,这些是相等的:

#![allow(unused_variables)]
fn main() {let s = String::from("hello");

    let slice = &s[0..2];
    let slice = &s[..2];
}

同样,如果分片蕴含字符串的最初一个字节,则能够删除尾随数字。这意味着这些是相等的:


#![allow(unused_variables)]
fn main() {let s = String::from("hello");

    let len = s.len();

    let slice = &s[3..len];
    let slice = &s[3..];
}

同样的能够删除这两个值以截取整个字符串。所以这些是相等的:

#![allow(unused_variables)]
fn main() {let s = String::from("hello");

    let len = s.len();

    let slice = &s[0..len];
    let slice = &s[..];
}

思考到所有这些信息后,让咱们重写 first_word 以返回切片。示意“字符串切片”的类型写为 &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[..]
}

同样的,返回切片也能够用于 second_word 函数:fn second_word(s: &String) -> &str {

然而仍旧有一个问题,就是 s.clear,废话不多说:

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(); // 出错!println!("第一个单词是: {}", word);
}

$ cargo run
   |
16 |     let word = first_word(&s);
   |                           -- immutable borrow occurs here
17 | 
18 |     s.clear(); // 出错!|     ^^^^^^^^^ mutable borrow occurs here
19 | 
20 |     println!("第一个单词是: {}", word);
   |                                       ---- immutable borrow later used here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0502`.
error: could not compile `ownership`.

To learn more, run the command again with --verbose.

从借阅规定中回想起,如果咱们对某物有不可变的援用,那么咱们也不能采纳可变的援用。因为 clear 须要截断 String,因而须要获取可变的援用。Rust 不容许这样做,并且编译失败。Rust 不仅使咱们的 API 易于应用,而且还打消了编译时的一整类谬误!(解决该问题的办法为,将 s.clear 语句放到最初)

字符串文字是切片

let s = "Hello, world!";
s 的类型是 &str:它是指向二进制文件的特定点的切片。这也是字符串文字不可变的起因。&str 是不可变的援用。

字符串切片作为参数
晓得能够宰割文字和字符串值后,这使咱们对 first_word 进行了另一项改良,这就是其签名:
fn first_word(s: &String) -> &str { 与其等价的写法是:fn first_word(s: &str) -> &str {

如果咱们有一个字符串切片,咱们能够间接传递它。如果咱们有一个字符串,咱们能够传递整个字符串的一部分。定义一个函数以采纳字符串切片而不是对 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 实用于 `String`s 的切片
    let word = first_word(&my_string[..]);

    let my_string_literal = "hello world";

    // first_word 实用于字符串文字的切片
    let word = first_word(&my_string_literal[..]);

    // 因为字符串文字 * 曾经是 * 字符串切片,所以它也能够应用,而无需应用切片语法!let word = first_word(my_string_literal);
}

Other Slices

其余类型的切片跟 String 类型的切片相似:


#![allow(unused_variables)]
fn main() {let a = [1, 2, 3, 4, 5];
    let slice = &a[1..3];
}

该切片的类型为 &[i32]。通过存储对第一个元素和长度的援用,它的工作形式与字符串切片雷同。

所属权总结:

所有权,借用和切片的概念可确保在编译时 Rust 程序中的内存平安。Rust 可让咱们管制内存应用状况,当数据所有者超出范围时,让数据所有者主动革除该数据意味着咱们不用再编写和调试额定的代码。

退出移动版