Call FnOnce several times?

Bellow simplified version of my code.
When tokio notify me about some event, I want to run my code (in real world I recieve data from channel, for simplification here I do just let data = Data).

The problem is the Cache is possible to use only in one thread simultaneously, and Cache::save is blocking.
I try my best, but code still impossible to compile.
The problem that I have MutexGuard after Mutex::lock and I can not use it several times.
tokio_threadpool::blocking accepts FnOnce so it is fine,
but poll_fn accepts FnMut and compiler not like it.

Of course I can write my poll_fn that accept FnOnce instead of FnMut,
but how I implement Future for it? Future::poll can obviously run several times?

use futures::{
    future::{poll_fn, Future},
    stream::Stream,
    sync::BiLock,
};
use std::path::Path;

fn main() {
    let cache = futures_locks::Mutex::new(Cache::new(Path::new("/tmp/cache.txt")));

    let fut = cache.lock().and_then(move |mut guard| {
        let data = Data;
        poll_fn(move || {
            tokio_threadpool::blocking(move || {
                println!("another thread");
                guard.save(data).unwrap();
            })
                .map_err(|_| panic!("the threadpool shut down"))
        })
    });    
}

struct Data;

struct Cache {
    _marker: std::cell::UnsafeCell<()>,
}

impl Cache {
    fn new(path: &Path) -> Cache {
        unimplemented!();
    }

    fn save(&mut self, data: Data) -> Result<(), ()> {
        unimplemented!();
    }
}
  1. To answer literal question from the title: you absolutely can't call FnOnce several times. This inability is part of a safety guarantee given by that type.

  2. The FnMut pain here is from using nested moves, which requires having an infinite number of copies of data. It's equivalent of:

let one_unique_copy_of_data = …;
call_many_times(move || drop(one_unique_copy_of_data)); 

An innner move || is same as drop(). In both cases the outer closure has to permanently lose access to the moved value. But if the first call to the closure gives up ownership of the value, there won't be any value to be used when FnMut is called for the second time. So that's a logical impossibility. Your closure can't have a cake and move it.

If you need owned value really one time inside FnMut, then the usual workaround for this is to use reference to Option, and option.take():

let optional = &mut Some(one_optional_copy_of_data);
call_many_times(|| drop(optional.take()));

This way the first call gets to own Some(val), and the rest gets None.

  1. poll_fn + blocking is the wrong model to use here. You don't take advantage of their benefits, but pay for their limits. If you use spawn_fn instead, you'll get a Future straight from FnOnce.
2 Likes

According to this CpuPool is not there anymore.So going with ThreadPool seems the right way. It's a pity there is no baked in functionality to mirror CpuPool though.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.