Matching on a member variable, both own and borrow

I have a struct Job that contains an enum JobType. The enum variants contain closures -- simplified (C is really a generic, but it's not important):

struct OneShotData {
  task: Box<dyn FnOnce(&C) -> JobResult + Send>
}
struct RecurringData {
  task: Box<dyn Fn(&C) -> JobResult + Send>
}
enum JobType {
  OneShot(OneShotData),
  Recurring(RecurringData)
}
struct Job {
  jinfo: JobType
}

(The recurring bit is new; earlier only one-shot jobs were supported).

As implied by their names, OneShot is run once (note FnOnce) while Recurring can run more than once (note Fn).

Jobs are stored in a HashSet, and when a Job is run it is first removed from the HashSet, then its task is run on a separate thread, which does something like this:

let res = match job.jinfo {
  JobType::OneShot(os) => (os.task)(&ctx),
  JobType::Recurring(ref r) => (r.task)(&ctx)
};

The issue is that for the Recurring case, I want to put job back into the HashSet whence it came, but clearly it can't because a partial move happened. If I make it a match &job.jinfo, then the OneShot case won't work, since its os.task is an FnOnce.

Reworking this to using if let solves one problem, but the borrow-checker is (understandably) unaware that restore_it is intended to imply that no partial borrows have happened:

let mut restore_it = false;
let res = if let JobType::OneShot(os) = job.jinfo {
  (os.task)(&mgr, &sh.ctx)
} else if let JobType::Recurring(r) = &job.jinfo {
  restore_it = true;
  (r.task)(&mgr, &sh.ctx)
} else {
  panic!("Unhandled");
};
if restore_it {
  idle_jobs.insert(job);
}

How can I handle both these cases? For the OneShot I want to consume the FnOnce, so I don't care that partial moves happened. For the Recurring I don't want any partial moves, because I want to store it [the job object] back into the HashSet.

I know this is simplified example code, but if your Job type is not too complicated, you can always destruct the old object by pattern matching, and later re-construct a new object and insert back into the HashSet.

1 Like

Full destructor Job and recreate a new in RecurringData case.
Have jsinfo be an Option you take from.
Have OneShotData::task an Option you take from.
Wrap OneShotData type in a Fn.
Don't use closures.
Swap when OneShotData for fake OneShotData/RecurringData/new-variant.

Just in order they came into my head. Looking back, swapping (std::mem:replace) seems like the one a chat bot should be recommending.

1 Like