Let's say I have a function that loads a configuration:
use core::future::Future;
struct Config;
async fn load(path: &Path) -> io::Result<Config> {
unimplemented!()
}
Now I'd like to abstract out the configuration loading piece in some other code, so I'd like to be able to pass a load
implementation as a parameter:
async fn load_using<Fun, Fut>(load: Fun) -> io::Result<Config>
where
Fun: Fn(&Path) -> Fut,
Fut: Future<Output = io::Result<Config>>,
{
let path = PathBuf::from("foo");
load(&path).await
}
So far this compiles.
However, I cannot use it:
async fn test_1() {
load_using(load).await;
}
async fn test_2() {
load_using(|path: &Path| async { load(path).await }).await;
}
error[E0308]: mismatched types
--> src/lib.rs:789:5
|
789 | load_with(load).await;
| ^^^^^^^^^^^^^^^ one type is more general than the other
|
= note: expected trait `for<'r> <for<'r> fn(&'r Path) -> impl for<'r> futures::Future<Output = std::result::Result<config::Config, std::io::Error>> {load} as FnOnce<(&'r Path,)>>`
found trait `for<'r> <for<'r> fn(&'r Path) -> impl for<'r> futures::Future<Output = std::result::Result<config::Config, std::io::Error>> {load} as FnOnce<(&'r Path,)>>`
note: the lifetime requirement is introduced here
--> src/lib.rs:780:23
|
780 | Fun: Fn(&Path) -> Fut,
| ^^^
error: lifetime may not live long enough
--> src/lib.rs:793:29
|
793 | load_with(|path: &Path| async { load(path).await }).await;
| - - ^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
| | |
| | return type of closure `impl futures::Future<Output = std::result::Result<config::Config, std::io::Error>>` contains a lifetime `'2`
| let's call the lifetime of this reference `'1`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `shadow` due to 2 previous errors
I guess the problem is that the compiler assumes that my Path reference will live only for the lifetime of the lambda call, but not for the lifetime of the future returned from the lambda. If I force the lambda to take ownership by cloning the data, it works:
async fn test_3() {
load_with(|path: &Path| {
let path = path.to_path_buf();
async move { load(&path).await }
}).await;
}
But if this is the only way out, then using reference makes no sense and I could just as well accept PathBuf
as the input.
Is there any way to do that without cloning?
Basically I'd like to be able to tell the compiler that the &Path
passed to the load_with
function will always live at least as long as any future created by the load
function.