Is it possible to specify the return type of an async block

I have the following async code block for which Rust is unable to infer the type information (although it is unclear to me why exactly that is the case).

let connect = async move {
    let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0)).await?;
    socket.connect((addr, 3054)).await?;
    Ok(socket)
};

I can solve this problem by providing additional type information indirectly on a following line by writing something like:

let connection_result : io::Result<UdpSocket> = connect.await;

Is it possible to annotate the async block so that Rust has enough information to infer the types without writing this additional line?

The problem here, as I understand it, is that the return type is unknown to me, that is, it is some type generated by the compiler that implements:

Future<Output = io::Result<UdpSocket>>

I am pretty sure it is not possible to write the following since (I think) connect does not implement the Sized trait and can not be stored on the stack:

let connect : impl Future<Output = io::Result<UdpSocket>> = async move {
    let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0)).await?;
    socket.connect((addr, 3054)).await?;
    Ok(socket)
};

The fact that the following works seems to corroborate this interpretation of the situation:

let connect : Box<dyn std::future::Future<Output = io::Result<UdpSocket>>> = Box::new(async move {
    let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0)).await?;
    socket.connect((addr, 3054)).await?;
    Ok(socket)
});

If someone could let me know if my understanding here is correct, that would be greatly appreciated!

It’s not so much that it isn’t Sized as the impl keyword isn’t allowed in that position. You can write a macro to verify the trait implementation:

macro_rules! assert_has_impl {
    ($e:ident : $($bound:tt)*) => {
        {
            fn assert_impl<T:$($bound)*>(_:&T) {}
            assert_impl(&$e);
        }
    }
}

let connect = async move {
    let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0)).await?;
    socket.connect((addr, 3054)).await?;
    Ok(socket)
};

assert_has_impl! { connect : impl Future<Output = io::Result<UdpSocket>> };

On nightly, the type_alias_impl_trait feature looks promising, but doesn’t currently help because an assignment statement can’t be the defining use.


Edit: Type-annotating the Ok on the final line might be enough:

let connect = async move {
    let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0)).await?;
    socket.connect((addr, 3054)).await?;
    io::Result::<UdpSocket>::Ok(socket)
};
2 Likes

No, there's no syntax for doing so directly. You can use Result::Ok(socket) if you import the io::Result alias, or Result::<_, ErrorType>::Ok(socket).

2 Likes

Indeed, type annotating the Ok(socket) solves the described problem. Thanks Alice and 2e71828!

Could you elaborate/speculate on why impl is not allowed in that position? Is it a design choice or is there a concrete reason why this would not be a good idea?

impl Trait is a relatively new language feature, and it was implemented for only a few specific places at first. My understanding is that it should become valid in more places over time. There are subtle details that need to be worked out for each new position, though, particularly in regards to how it interacts with other language features.

It's also important to let the small design settle enough that best practices can evolve around it. The expansion of the feature can then be designed with real-world usage patterns in mind instead of only theoretical ones.

1 Like