Pre-Pre-RFC: async methods & bounding async fns

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: ! and impl 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 be async 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:

  1. Putting the bound by the async connects it to the future type, rather than the interior return type.
  2. I’m worried that the : Send could get lost pretty easily when scanning (-> Vec<T>: Send where T: Send for example).
  3. I don’t want ambiguity issues if we ever want to use a : as a part of our type name grammar someday.
11 Likes