Home > OS >  Any way to make Rust understand an `fn(T) -> impl Future` always returns the same type?
Any way to make Rust understand an `fn(T) -> impl Future` always returns the same type?

Time:05-28

I've a function which takes a generic parameter, gets what it needs out of that, then returns a future. The future does not actually store the generic data, it is completely monomorphic.

For convenience I wanted to use an async fn to create the future, which I understand requires returning an impl Future as async fn return opaque types: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c3b061d12a126dc30099ac3bd018c273

use std::io::{Read, stdin};
use std::fs::File;
use std::future::Future;
use std::path::Path;

fn caller(p: Option<&Path>) -> impl Future<Output=()> {
    if let Some(p) = p {
        f(File::open(p).unwrap())
    } else {
        f(stdin())
    }
}

fn f<R: Read>(_: R) -> impl Future<Output=()> {
    fut()
}

async fn fut() {}

However rustc freaks out in the conditional as the caller side is absolutely convinced the future must somehow be different between the two branches:

error[E0308]: `if` and `else` have incompatible types
  --> src/lib.rs:10:9
   |
7  | /     if let Some(p) = p {
8  | |         f(File::open(p).unwrap())
   | |         ------------------------- expected because of this
9  | |     } else {
10 | |         f(stdin())
   | |         ^^^^^^^^^^ expected struct `File`, found struct `Stdin`
11 | |     }
   | |_____- `if` and `else` have incompatible types
...
14 |   fn f<R: Read>(_: R) -> impl Future<Output=()> {
   |                          ---------------------- the found opaque type
   |
   = note:     expected type `impl Future<Output = ()>` (struct `File`)
           found opaque type `impl Future<Output = ()>` (struct `Stdin`)

Is there a way to fix this aside from boxing the future or hand-rolling fut in order to end up with a single concrete type?

CodePudding user response:

I don't think you can avoid boxing, but at least you can avoid boxing the future itself:

use std::io::{Read, stdin};
use std::fs::File;
use std::future::Future;
use std::path::Path;

fn caller(p: Option<&Path>) -> impl Future<Output=()> {
    let read = if let Some(p) = p {
        Box::new(File::open(p).unwrap()) as Box<dyn Read>
    } else {
        Box::new(stdin())
    };
    f(read)
}

fn f<R: Read>(_: R) -> impl Future<Output=()> {
    fut()
}

async fn fut() {}

As my understanding, the problem is not the future, but that is actually building different types for the parameters, and somehow floods into the return type. It would makes sense it wouldn't though.

Playground

  • Related