I'm trying to write a trait which receives an Iter<Item = T>
of work items, filters and then applies anything outstanding, and records the results. The trait contains some state (a connection to an external service). Because the definition of the methods is part of a trait, the return types are variations on Box<dyn Iter<Item = (Status, Work)>
as a concession to the inability to use -> impl Iterator
in traits yet.
I eliminated the trait DoesWork
to keep the code simple, but assume the entire impl OneKindOfRunner
is impl DoesWork for OneKindOfRunner
, please (in the Rust playground):
struct OneKindOfRunner {
// conn: in reality this conn is mutable, and causes to need mut refs
// to the OneKindOfRunner. The WorkRunner keeps a state, and that is
// in a database backed by this conn, so work, filter and do_one_work
// all need to communicate with the database (hence self)
//
// I don't know if I can reasonably implement Copy()
// as E0505 suggests, I prefer not right now.
}
#[derive(Debug)]
struct Work {
// some static stuff, no mutable state, really.
}
type WorkStatus = (bool, Work); // does the work need to happen?
type WorkResult = (bool, Work); // did we do the work successfully?
// In the real app, OneKindOfRunner implements a Runner
// trait, hence the Box<>ed dyn return types.
impl OneKindOfRunner {
fn new() -> Self {
OneKindOfRunner {}
}
fn do_one_work<'a>(&'a mut self, work_item: Work) -> WorkResult {
(true, work_item) // return tuple indicating success or not (distinct from Result<T,E>)
}
fn filter_pending_work<'a>(
&'a mut self,
work: impl Iterator<Item = Work> + 'a,
) -> Box<dyn Iterator<Item = WorkResult> + 'a> {
Box::new(work.map(|w| (false, w)).into_iter())
}
fn work<'a>(
&'a mut self,
work: impl Iterator<Item = Work> + 'a,
) -> Result<Box<dyn Iterator<Item = WorkResult> + 'a>, ()> {
Ok(Box::new(
self.filter_pending_work(work)
.map(move |(_status, work)| self.do_one_work(work))
.into_iter(),
))
}
}
fn main() {
let work = vec![Work {}, Work {}, Work {}, Work {}].into_iter();
let mut runner = OneKindOfRunner::new();
match runner.work(work) {
Ok(results) => {
for (success, work) in results {
println!("Result was {} for work {:?}", success, work)
}
}
Err(_) => panic!("something err'd"),
};
}
The code errs with the following:
error[E0505]: cannot move out of `self` because it is borrowed
--> src/main.rs:43:22
|
37 | fn work<'a>(
| -- lifetime `'a` defined here
...
41 | / Ok(Box::new(
42 | | self.filter_pending_work(work)
| | ---- borrow of `*self` occurs here
43 | | .map(move |(_status, work)| self.do_one_work(work))
| | ^^^^^^^^^^^^^^^^^^^^^^ ---- move occurs due to use in closure
| | |
| | move out of `self` occurs here
44 | | .into_iter(),
45 | | ))
| |__________- returning this value requires that `*self` is borrowed for `'a`
With or without the move
on #43, I cannot find a way to make this work.
I thought the &'a mut self
, and the Result<Box<dyn Iterator<Item = WorkResult> + 'a>, ()>
would ensure that the lifetimes of the result (which implies a dependency on the iterator closure?) are tied to the longevity of self
(e.g I must consume the iterator before allowing the self
to go out of scope, whoever is calling my work()
function)
I believe the problem is stemming from using self.<some fn>
twice (nested) which I don't know a good alternative for.
In reality we don't want to consume the Iter<Item = Work>
, and to be able to cleanly map()
over it once.
fn work<'a>(
&'a mut self,
work: impl Iterator<Item = Work> + 'a,
) -> Result<Box<dyn Iterator<Item = WorkResult> + 'a>, ()> {
Ok(Box::new(
self.filter_pending_work(work)
.map(move |(_status, work)| self.do_one_work(work))
.into_iter(),
))
}
Assigning the result of self.filter_pending_work(work)
outside the box doesn't help, removing the move (_status, work)
leads to some variation on:
44 | | .map(|(_status, work)| self.do_one_work(work))
| | ^^^^^^^^^^^^^^^^^ ---- second borrow occurs due to use of `self` in closure
| | |
| | closure construction occurs here
45 | | .into_iter(),
46 | | ))
| |__________- returning this value requires that `*self` is borrowed for `'a`
Is there any reasonable way around this? Are Iterator
s the right thing to do here, or should I just scrap the whole idea and work with a Box<Vec<Work>>
? It feels like Iterator<Item = Foo>
is the more correct thing to use because of Rust's strong APIs for filtering and mapping, but trying to work with iterators (rather than just use into_iter()
on a Vec<T>
is really quite painful.