rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
99.01k stars 12.79k forks source link

Tracking Issue for return type notation #109417

Open compiler-errors opened 1 year ago

compiler-errors commented 1 year ago

This is a tracking issue for the feature return type notation specified in rust-lang/rfcs#3654. The feature gate for the issue is #![feature(return_type_notation)].

About tracking issues

Tracking issues are used to record the overall progress of implementation. They are also used as hubs connecting to other relevant issues, e.g., bugs or open design questions. A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature. Instead, open a dedicated issue for the specific matter and add the relevant feature gate label.

Steps

Unresolved questions

Experimental results

Part of the goal of the experimental process is to identify benefits but also concerns that need to be addressed in the RFC.

Implementation history

VictorBulba commented 1 year ago

hey

Is there a way to use this the following way?

#![feature(async_fn_in_trait)]
#![feature(return_type_notation)]

use std::future::Future;

trait Inner: Send + Sync {
    async fn do_async(&self);
}

trait Outer {
    type In: Inner<do_async(): Send>;
} 

fn foo<T>(inner: T::In) -> impl Future + Send
where
    T: Outer,
{
    async move {
        inner.do_async().await;
    }
}

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=d69310a05735ec25356754d4b8bdd708

programmerjake commented 5 months ago

somewhat related: we may want to change async functions to return impl IntoFuture instead of impl Future so we can eventually replace Pin with !Move as outlined here -- having Send bounds everywhere on the direct return type which would be impl IntoFuture with that change but not the Future type makes that edition change much more difficult, so imo we should consider alternatives, one of which is somehow making Trait::my_async_fn(..): Send also imply the Future is Send either by adding that bound as a desugaring or requiring the new Pin-less IntoFuture to have Self: Send imply Self::IntoFuture: Send

breeo7 commented 2 months ago

Sorry, I didn't see the RFC until it was merged. Just a quick comment for your consideration.

The chosen syntax might be confusing bounds on the function itself with bounds on the return type of the function? This could come back and bite future Rust if trait bounds are made possible for functions (as effects for example):

fn restricted_function<T: Foo>()
where
    T::method(..): NoPanic + NoAlloc // Bounds on the function itself

In contrast to:

fn restricted_return_type<T: Foo>()
where
    T::method(..) -> Send + 'static // Bounds on the return type
slanterns commented 2 months ago

The parentheses clearly indicates it's the eval result (output, return type) of the function, not the function itself.

maboesanman commented 1 week ago

I wonder if a more discoverable and comfortable syntax for this idea would be the ReturnType<F> notation from typescript. It may make the syntax more uniform in where clauses, and I believe there is some precedent for "magic" generics like this in the rust language, specifically dyn metadata.

An example then would be:

trait SomeTrait {
    async fn some_func()
}

impl<T> SomeStruct<T> 
where
    T: SomeTrait,
    ReturnType<T::some_func>: SomeBound,
{

}

I think it would be usable in all the same places as T::some_func(..): style bounds, but it establishes a pattern that is more readable (in my opinion) and easier to extend if we needed to express other type transformations (for example, the tuple type of a function's arguments).

If we want to indicate these are not regular generic types, something like @ReturnType<..> or some other identifying character could be used to indicate the operation is more complex than generic instantiation.

In some sense, generic instantiation is a function that takes types and produces types, and that's what the return type syntax is trying to do. The only difference is that the output is less complex than the input, which is usually not true for generics.

(Originally I mistakenly commented this on the initial support issue before, my mistake)

onestacked commented 1 week ago

How would that look for dyn Trait (or impl Trait) like: dyn Trait<method(..): Send>

I think dyn Trait<ReturnType<method>: Send>> is a bit weird.

maboesanman commented 1 week ago

How would that look for dyn Trait (or impl Trait) like: dyn Trait<method(..): Send>

I think dyn Trait<ReturnType<method>: Send>> is a bit weird.

I don't think bounds on associated types are supported in that way are they? In that situation I think you'd do a separate trait for use with dyn that is auto implemented and has the specified bounds.

How is that resolved with the currently proposed syntax?

compiler-errors commented 1 week ago

Return type notation is definitely supported in associated type bound syntax. It's not allowed in dyn, but I expect it could be in the future. I don't think requiring a user to make a new trait (even if we had trait aliases) is an acceptable solution.

I don't think that the proposed ReturnType<T> really simplifies things. It gives a perception of being just a regular type, but frankly it's really weird on its own. Firstly, ReturnType<T::method> is in general a higher ranked type. It'll be some for<...> over all of the lifetime generics of the method it's referring to -- That's why RTN is only allowed in where clauses today.

Secondly, the generic parameter it would take (e.g. T::method) is not in the same namespace as types, meaning that ReturnType would need to a completely different type construct so that the name resolver can understand it correctly. Since we're overloading regular "path" type (e.g. Foo<T>) syntax for this, it definitely does not make it easy to implement, and even worse I fear it doesn't help with the readability of the bound either.

Setting aside a totally different syntax for RTN is part of what makes it easier to implement and understand. It's distinct.

Also, I don't really understand the parallel to DynMetadata; that's just a regular type that takes a generic type. That's not really magical.

maboesanman commented 1 week ago

I suppose my intention is to highlight that a type-system level construct for specifying the return type of a function type is an operation that belongs to class of type-manipulations which may make sense to anticipate growing in number.

Expressing "the return type of a function type" has a very specific syntax right now that may make the addition of something like "the tuple type of arguments to a function type", or even more exotic things like "the tuple you get when you omit the first item" necessarily inconsistent from a syntax perspective.

My suggestion is not that the proposed syntax is bad on its own, but that opting for a more general "function that operates on types" style syntax would be more extensible should those other extensions be added later. If the type system becomes more generally expressive, having a very application specific syntax for this feature may result in forced future language inconsistency.

This syntax change suggestion could be implemented in an edition (I think?) should those other extensions be considered, so I'm probably bike-shedding here anyway.