Thanks for writing up your view of our convo, @aturon. I’m going to avoid the interchangeability issue because I don’t know how to express my thoughts just yet, and just respond to the two other comments:
Naming anonymous type
I’m comfortable with outputof(path)
as the syntax as long as we have an easier form for introducing the bound at the def site, such as async(Send)
. That is, I think
where T: Trait, outputof(T::method): Send
Is a very comprehensible way to write this bound. (I don’t have a strong opinion about casing and delimeters etc for outputof and typeof).
I do prefer outputof to typeof()::Output
. The fact that outputof is sugar for a special case of typeof is not really a compelling argument against it in my opinion. Its clearly the most common use for typeof & I think its much easier to understand the first time you see it.
async at the end
None of this issue of adding the Send bound was unknown when we made the decisions about where async goes, and I don’t think anything here suggests we change it out. I’d prefer not to re-open this question.
Overally, I don’t think this:
fn foo() -> async i32 + Send
Is any more clear than this:
async(Send) fn foo() -> i32
Both are unique, ungeneralizable syntactic sugars. I’d make these additional notes:
- The asyncness is a property of the function, not the return type. There is no such type as
async i32
. When we’ve had special return types, we’ve tended to want to generalize them to other type positions:!
andimpl Trait
are good examples. -
async i32 + Send
is not similar to how type syntax works otherwise. In addition to being a totally unique way of saying a type is bounded (shouldn’t it beasync i32: Send
?), its grammatically ambiguous with non-dyn
trait types, which I think are still allowed just linted against, and in general we have not allowed types to be followed by+
in our grammar. - I want to limit the deviation from the standard async function syntax as much as possible. Other languages put the async before the function declaration & this is what users expect (its called “async functions” after all, not “async return types”). This is a big deal to me that I think is often overlooked in these discussions: we can only deviate so much from the ‘platonic norm’ before it starts to lose some of the advantages of async/await.
- I don’t want to re-open the name of the Future trait; as we’ve discussed before, I think there’s a lot of built up understanding and “brand” around that trait name that I wouldn’t want to give up.
I did consider this alternative:
async fn foo() -> i32: Send { }
I prefered async(Send)
for a couple of reasons:
- Putting the bound by the
async
connects it to the future type, rather than the interior return type. - I’m worried that the
: Send
could get lost pretty easily when scanning (-> Vec<T>: Send where T: Send
for example). - I don’t want ambiguity issues if we ever want to use a
:
as a part of our type name grammar someday.