Hacker News new | ask | show | jobs
by slopinthebag 18 days ago
Say you have a function like validate_user(). In Go, should you block the main thread on this call, or fork and join? What if it makes a DB request and now your UI is blocked for 2 seconds or something? You need to know implicitly you need to call validate_user() in a goroutine, and then deal with forking and joining manually. If it's explicitly coloured as async, you know.

In most async/await languages you can run async functions as sync, eg. Tokio's block_on method or C#'s .Result.

1 comments

I think you're missing something. Go's functions are all blue, not all red. As far as I know, the callers of async functions with Rust's Tokio and C#'s Tasks themselves need to be async; not the case for Go.

Concurrency is usually a mix of goroutines and channels. There is no inherent link between caller and asynchronous callee. You can use goroutines without channels, and channels without goroutines.

You can write "go" to launch any function call in its own goroutine, but you cannot get a return value from it. This isn't valid:

    user_is_valid := go validate_user(u)
The idiomatic way you can do that is to use a goroutine and a channel:

    ch := make(chan bool)
    go func() {                  // runs asynchronously
        ch <- validate_user(u)   // blocks until it can send
    }()
    user_is_valid := <-ch        // blocks until it can receive
    return user_is_valid         // ta-da, blue function returning red function result
I don't think it's a boon to have functions "coloured for you" to tell you that they might block. On the other hand, functions that would block tend to accept a Context parameter to let you control what they should do. It's a major indicator that the function's probably going to do something async, but it doesn't have to.
Well, the benefit of async/await is that you can just do

   let user_is_valid = validate_user(u).await
But you can also pass around future values. It's pretty dang ergonomic, the tradeoff is that it requires more ceremony to block on async functions (and is not even possible in JS). This was considered a potential problem 10 years ago but we've discovered since than that it's not really an issue at all.

My point about the colouring is that it's actually nice to have explicit colouring, in go the asynchronicity of functions aren't encoded by the type system but in practice you still handle them differently. You can't just call one of them without passing in things like context, or handling channels without refactoring anyways.

I think we're just seeing different sides of the same coin. Go's idiomatic code would be:

    user_is_valid := validate_user(u)
i.e. you have no special handling needed, it fits anywhere. You don't even know async is or isn't involved, and it wouldn't matter for correctness if it did. If you want to make it async, you get to do that yourself.

However, the evolution of that design has resulted in use of shared and nested Contexts in order to coordinate state and lifespans across goroutines, which is why they expose this parameter. And if you don't care, you can always use context.Background() for the value. Not only does it provide that synchronisation, but also it made cancellation and timeouts simple and standardised (obviously, async frameworks of other languages have their own idioms for this)