4 ms·
> foo(); <-- doesn't block Only if you know that foo is an async function. You can't tell by the function call itelf. > warning: unused implement
by eis 3y ago
> foo(); <-- doesn't block
Only if you know that foo is an async function. You can't tell by the function call itelf.
> warning: unused implementer of `futures::Future` that must be used
Interesting, I haven't seen this warning in the Rust codebase I worked a little with. I'll have to check the compiler settings. Anyways wouldn't it make sense to actually throw an error instead of just a warning?
> Additionally there are certain things you are not allowed to keep across await points, e.g. mutex guards or other stuff that's not safe to switch between threads. E.g. using a thread-local data structure across await points might break, because you could be on a different thread after await. If await was hidden, you'd likely be much more surprised when the compiler would reject some code due to "invisible" await.
Why couldn't the compiler clearly state the reason for the error though?
- pkolaczk 3y ago> Only if you know that foo is an async function. You can't tell by the function call itelf. That's fair point, but traditionally you don't use blocking functions in async contexts at all. It is fairly easy to lint for by prohibiting some inherently blocking calls eg.g std::io, although they might sneak in through some third-party dependency. This doesn't have an easy solution because Rust is a general purpose language that allows different styles of concurrency adapted best to the situation, instead of one-size-fits-all like Golang. Rust has means to annotate functions so maybe there will be some automation to deal with that in the future, similar to how `#[must_use]` works now. E.g. `#[blocking]` or whatever. > I'll have to check the compiler settings. This is with default compiler settings. > Why couldn't the compiler clearly state the reason for the error though? Stating the reason is probably solvable problem, but there is another problem: what if 5 layers down the call chain something suddenly introduces a potentially blocking (awaiting) operation? This would mean that some code that previously compiled now has to stop compiling even though it hasn't changed and even though none of the signatures it uses changed. I guess it would break things like separate compilation. And again, it would be less readable than it is now. Now it is fairly simple - you don't have to look down the call chain to know that something can do await.
- eis 3y agoYou can't change the async/await rules of Rust anymore. I get that. But if it started like I described from the beginning I don't see why that wouldn't work. It's just a question of syntax. Someone adding a blocking call 5 layers down wouldn't be any different than someone adding an "await foo()" right now. Code would still compile fine. As long as everything follows the same rules. Can't mix them obviously.
- pkolaczk 3y ago> wouldn't be any different than someone adding an "await foo()" right now It would. `.await` works only inside `async` context. So if the method wasn't async at the top level, then adding `.await` somewhere down the call chain would force changing all the signatures up to now become `async`. So you cannot just freely add `.await` at random places that don't expect it. Which is sometimes a blessing and sometimes a curse. Definitely when trying to hack a quick and dirty prototype this is a slowdown. But it is really good when you aim for low latency and predictability.
- pornel 3y ago> You can't tell by the function call itelf. You can't know that in general. Any regular Go function could spawn a goroutine return immediately too. In JS a "blocking" function could call setImmediate(…) and return too. Even in C, a function could spawn a thread and return immediately too. You never know at the call site whether a function will block or not, in any language. So I think polled futures actually are closest to knowing this, since the block-or-not decision can be bubbled up to the caller. In Rust the "doesn't block" example would more likely be `runtime.spawn(foo())`, since the executor is not built into the language, so spawning asynchronously is easier when left up to the caller.