The question seems to be: why is asynchrony not a requirement that can be specified by a protocol? In other words, why is a protocol not allowed to say: “you must implement this asynchronously”, instead it can only say: “you must implement this synchronously” or “you are allowed to implement this asynchronously or not”?
It’s a similar question with throwing. Why is a protocol allowed to say “you must implement this without throwing” or “you may implement this with or without throwing” but a protocol cannot say, “you must implement this with throwing”?
Well if asynchrony were a requirement mandated by a protocol, would it be satisfied if you simply added the async
keyword to a function, but didn’t change anything else… meaning the function doesn’t await
anywhere? If so, what are you gaining by requiring implementers to mark their methods as async
but not make use of asynchrony? What you lose is that in code that works directly with a concrete type, it now has to await
those calls (and so it can’t call the function in sync code) even though it really doesn’t need to, because there’s no actual suspending in the function.
Same deal with throwing: if a protocol mandated that implementers must mark their functions as “throws”, then implementers that don’t need to throw anything still wouldn’t, they would just mark their functions as throws
anyways. Again, what do you gain? What you lose is the ability to call the function without try
even though that’s unnecessary.
A function declaring throws
isn’t a promise it actually will ever throw anything (just as await
isn't a promise it will actually suspend). So really the protocol rules are just reflecting the meaning of those keywords: they always mean "this might happen so you need to prepare for it".
Ideally you only want to mark functions as async
if they need to suspend, and you only mark functions as throws
if they need to throw an error. It doesn’t make sense for a protocol to mandate that a function declare it suspends or throws. What might make sense is a protocol mandates non-functional requirements (NFRs) like “this call doesn’t block for more than x milliseconds”, which depending on what the function is supposed to do, might practically demand that it suspend… or an NFR that a function retrieve something over an unreliable channel (like a network), which practically demands it might throw. It’s a neat idea that one day we could tell the compiler about NFRs like that and have it synthesize the automated tests that prove all our implementations satisfy them, and I think in C++ land this is what contracts are supposed to do (I haven't used them so I'm not really sure, I just get that impression when reading about them).
Since you mentioned potential performance optimizations, maybe this is what you’re really after. So it’s not a matter of enforcing the async
keyword but really NFRs like not blocking for too long. If a concrete type can find a way to satisfy that without suspending, then it doesn’t need the async
keyword, but the NFR might make that practically impossible. Until there's a way to teach the compiler about those NFRs, you just need to write the tests yourself. So then write a test for your protocol that can run against any concrete implementation that checks it has the performance characteristics you need.