1

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?

Shepmaster
  • 274,917
  • 47
  • 731
  • 969
l0b0
  • 48,420
  • 21
  • 118
  • 185
  • Perhaps you would read [Is there any way to return a reference to a variable created in a function?](https://stackoverflow.com/q/32682876/155423) and then [edit] your question to explain how it's different? – Shepmaster Mar 12 '18 at 22:52
  • 2
    I haven't tried to compile (please post your errors instead), but the thing that seems immediately wrong is `.into_iter()`. That call consumes the original value. If you want `entries` to exist after you iterate them, you want [iter](https://doc.rust-lang.org/1.21.0/std/vec/struct.Vec.html#method.iter) instead to iterate over references. – viraptor Mar 12 '18 at 22:58
  • @Shepmaster It's different at least in that I'm passing in a reference to the object I'd like the response to refer to, `&self`. – l0b0 Mar 12 '18 at 23:18

1 Answers1

3

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> { /* ... */ }
Shepmaster
  • 274,917
  • 47
  • 731
  • 969
  • Awesome! The error about moving out of borrowed content pointed to the `self.entries` line, so I thought it was unrelated to the `into_iter` call. – l0b0 Mar 12 '18 at 23:45