rust 为什么这不是一个悬空指针?

djmepvbi  于 2022-12-19  发布在  其他
关注(0)|答案(1)|浏览(137)

我正在写《铁 rust 》这本书,它有this code snippet

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

在上一章中,我们已经解释过

  • rust编译器防止你在对象超出作用域(悬空引用)后保留对对象的引用,以及
  • 变量在它们被提及的最后时刻超出范围(所给出的例子示出了通过确保在创建可变引用之后不提及不可变引用来创建对同一块中的同一对象的不可变引用和可变引用)。

在我看来,在for行标题之后似乎没有引用bytes(假定与bytes.iter().enumerate()相关联的代码仅在循环开始之前执行一次,而不是在每次循环迭代上执行),所以&item不应该被允许作为对bytes的任何部分的引用。但是我没有看到任何其他对象(“对象”是正确的铁 rust 术语吗?)它可以成为一个参考。
s确实仍在作用域中,但是,在for循环滚动时,编译器是否还记得bytess之间的连接?实际上,即使我将函数更改为直接接受bytes,编译器也会认为一切都很好:

fn first_word(bytes: &[u8]) -> usize {
    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i+1;
        }
    }

    0
}

for循环头后面没有提到iitem以外的变量,所以看起来&item真的不能作为任何东西的引用!
我查看了this very similarly-titled question,那里的注解和答案暗示可能存在一个使bytes保持活动的lifetime参数,并提出了一种询问编译器它认为类型/lifetime是什么的方法,我还没有学习过lifetime,所以我在黑暗中摸索了一下,但我尝试了:

fn first_word<'x_lifetime>(s: &String) -> usize {
    let bytes = s.as_bytes();
    let x_variable: &'x_lifetime () = bytes;

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i+1;
        }
    }

    0
}

然而,这个错误虽然确实表明bytes是一个&[u8],我理解这一点,但似乎也暗示没有额外的生存期信息与bytes相关联。我这样说是因为这个错误在“预期”部分包括生存期信息,而在“发现”部分没有。

error[E0308]: mismatched types
 --> test.rs:3:39
  |
3 |     let x_variable: &'x_lifetime () = bytes;
  |                     ---------------   ^^^^^ expected `()`, found slice `[u8]`
  |                     |
  |                     expected due to this
  |
  = note: expected reference `&'x_lifetime ()`
             found reference `&[u8]`

那么这是怎么回事呢?显然我的推理有一部分是错误的,但是什么呢?为什么item不是悬空引用呢?

xyhw6mcr

xyhw6mcr1#

下面是第一个函数的一个版本,其中包含了 all 生存期和类型,并且for循环被替换为一个等效的while let循环。

fn first_word<'a>(s: &'a String) -> usize {
    let bytes: &'a [u8] = s.as_bytes();

    let mut iter: std::iter::Enumerate<std::slice::Iter<'a, u8>>
        = bytes.iter().enumerate();

    while let Some(iter_item) = iter.next() {
        let (i, &item): (usize, &'a u8) = iter_item;
        if item == b' ' {
            return i;
        }
    }

    s.len()
}

注意事项:

  • bytes.iter().enumerate()生成的迭代器类型有一个lifetime参数,它确保迭代器不会比它所迭代的[u8]更长寿(注意&[u8] * 可以 * 消失--它是不需要的。重要的是它的 referent,String内部的字节,仍然活着。所以,我们只需要考虑一个lifetime 'asbytes的生存期没有分开,因为String中只有一个字节片,我们用不同的方式引用它。
  • iter-对应于for的隐式操作的显式变量-在循环的每次迭代中使用。

我看到了另一个误解,不完全是关于寿命和范围的:
可能存在使bytes保持存活的寿命参数,

**生存期永远不会让某些东西保持存活。**生存期永远不会影响程序的执行;在某些类型中,如&'a u8,生存期是一个编译时 * 声明,该类型的值将在该生存期内有效 *。更改程序中的生存期只会更改 * 编译器要证明(检查)的内容 *,而不会更改 * 关于程序的真实内容 *。

相关问题