简体   繁体   中英

Why does borrow checker complain about the lifetimes of these different slices?

Why, in the code below, does using an array slice work, but using a slice of a Vec doesn't?

use rand::{rngs::adapter::ReadRng, RngCore};
use std::io::Read;

struct MyRng {
    rng: Box<dyn RngCore>,
}

pub fn main() {
    // Version 1: error
    //
    let data = Vec::<u8>::new();
    let data_slice = data.as_slice();

    // Version 2: works
    //
    // let data_slice = &[0_u8][..];

    // Version 3: error (!?!)
    //
    // let data = [0_u8];
    // let data_slice = &data[..];

    let read = Box::new(data_slice) as Box<dyn Read>;
    let rng = Box::new(ReadRng::new(read));

    // With this commented out, all versions work.
    MyRng { rng };
}

There are a few things that puzzle me:

  • What's the difference between the three approaches, if all are in the same scope?
  • The error says that data is dropped while borrowed, but points to the end of the scope - isn't everything dropped by then?
  • Why if I remove the MyRng instantiation, everything works fine?

The Rust Reference on Lifetime Elision:

If the trait has no lifetime bounds, then the lifetime is inferred in expressions and is 'static outside of expressions.

So by default boxed trait objects get a 'static bound. So this struct:

struct MyRng {
    rng: Box<dyn RngCore>,
}

Actually expands to this:

struct MyRng {
    rng: Box<dyn RngCore + 'static>,
}

Which forces you to produce either an owned type or a 'static reference in order to satisfy the bound. However, you can opt out of the implicit 'static bound entirely by making your struct generic, after which all the different versions of the code compile:

use rand::{rngs::adapter::ReadRng, RngCore};
use std::io::Read;

struct MyRng<'a> {
    rng: Box<dyn RngCore + 'a>,
}

pub fn main() {
    // Version 1: now works
    let data = Vec::<u8>::new();
    let data_slice = data.as_slice();
    let read = Box::new(data_slice) as Box<dyn Read>;
    let rng = Box::new(ReadRng::new(read));
    let my_rng = MyRng { rng };

    // Version 2: still works
    let data_slice = &[0_u8][..];
    let read = Box::new(data_slice) as Box<dyn Read>;
    let rng = Box::new(ReadRng::new(read));
    let my_rng = MyRng { rng };

    // Version 3: now works
    let data = [0_u8];
    let data_slice = &data[..];
    let read = Box::new(data_slice) as Box<dyn Read>;
    let rng = Box::new(ReadRng::new(read));
    let my_rng = MyRng { rng };
}

playground


To answer your individual questions more directly:

What's the difference between the three approaches, if all are in the same scope?

Scope and lifetimes aren't the same thing, but the main difference between the 3 approaches is that approach #2 creates a static slice. When you hardcode some &T reference into your code without it referring to any data owned by any variable then it gets written into the read-only segment of the binary and gets a 'static lifetime.

The error says that data is dropped while borrowed, but points to the end of the scope - isn't everything dropped by then?

Yes, but your type, by definition, requires the passed value to be bounded by a 'static lifetime, and since approaches #1 and #3 do not produce such values the compiler rejects the code.

Why if I remove the MyRng instantiation, everything works fine?

Because there's nothing wrong with the definition of your MyRng struct, only when you try to instantiate it incorrectly does the compiler complain.

[0_u8] is a constant, so it undergoes rvalue static promotion , allowing references to it to have a static lifetime, so it won't be dropped. Your code fails because you have a slice that borrows the local variable data yet tries to create a MyRng variable from it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM