How to return a struct with a reference to self in

2020-04-19 06:24发布

问题:

I've implemented a struct which has a list of crontab entries, each of which knows its own recurrence (such as */5 * * * * in crontab):

extern crate chrono;

use chrono::NaiveDateTime;

pub struct Crontab<'a> {
    entries: Vec<Entry<'a>>,
}

pub struct Entry<'a> {
    pub recurrence: Recurrence,
    pub command: &'a str,
}

pub struct Recurrence {
    minutes: Vec<u8>,
    hours: Vec<u8>,
    days_of_month: Vec<u8>,
    months: Vec<u8>,
    days_of_week: Vec<u8>,
}

Based on the current time you can get the next occurrence of a command:

impl Recurrence {
    pub fn next_match(&self, after: NaiveDateTime) -> NaiveDateTime {
        unimplemented!()
    }
}

I'm trying to write a function on Crontab to get the Entry which will run next (that is, for which recurrence.next_match() is the lowest).

impl<'a> Crontab<'a> {
    fn next_run(&self, from: NaiveDateTime) -> Run<'a> {
        &self.entries
            .into_iter()
            .map(|entry| Run {
                entry: &entry,
                datetime: entry.recurrence.next_match(from),
            })
            .min_by(|this, other| this.datetime.cmp(&other.datetime))
            .unwrap()
    }
}

struct Run<'a> {
    entry: &'a Entry<'a>,
    datetime: NaiveDateTime,
}

This generates the error:

error[E0308]: mismatched types
  --> src/main.rs:30:9
   |
29 |       fn next_run(&self, from: NaiveDateTime) -> Run<'a> {
   |                                                  ------- expected `Run<'a>` because of return type
30 | /         &self.entries
31 | |             .into_iter()
32 | |             .map(|entry| Run {
33 | |                 entry: &entry,
...  |
36 | |             .min_by(|this, other| this.datetime.cmp(&other.datetime))
37 | |             .unwrap()
   | |_____________________^ expected struct `Run`, found &Run<'_>
   |
   = note: expected type `Run<'a>`
              found type `&Run<'_>`

Similar variants I've tried fail to compile with messages such as "cannot move out of borrowed content" (if changing the return type to &Run<'a>) or that the &entry does not live long enough.

It seems to make most sense that the Run should have a reference to rather than a copy of the Entry, but I'm not sure how to juggle both the lifetimes and references to get to that point (and I don't know whether 'a refers to the same lifetime in both structs). What am I missing here?

回答1:

As described in Is there any way to return a reference to a variable created in a function?, you cannot create a value in a function and return a reference to it. Nothing would own the result of your iterator chain, thus the reference would point at invalid data.

That doesn't even really matter: as pointed out in the comments, you cannot call into_iter on self.entries because you cannot move out of borrowed content to start with, as described in Cannot move out of borrowed content. This means that we cannot have an owned value of an Entry as the result of the iterator chain to start with.

Crontab owns the Entry; as soon as the Crontab moves, any reference to any Entry becomes invalid. This means that any references need to be tied to how long self lives; the generic lifetime 'a cannot come into play:

fn next_run(&self, from: NaiveDateTime) -> Run {
    self.entries
        .iter()
        .map(|entry| Run {
            entry,
            datetime: entry.recurrence.next_match(from),
        })
        .min_by(|this, other| this.datetime.cmp(&other.datetime))
        .unwrap()
}

Or the explicit version:

fn next_run<'b>(&'b self, from: NaiveDateTime) -> Run<'b> { /* ... */ }


标签: rust