-
Notifications
You must be signed in to change notification settings - Fork 562
Description
Proposal
1. Syntax Usage
fut := go fn(args...) // Start concurrent task, return channel
x := <-fut // Get return value
x := <-go fn(args...) // Direct combination
<-go fn(args...) // Wait for completion (no return value)
go fn(args...) // Fire-and-forget (original Go syntax)Features:
goexpression returnschan T, fully compatible with Go channel syntax- Can be used in
selectstatements
Constraints:
- fut can only be received (
<-) once — compile error if used multiple times - For void functions:
x := <-go fnRetVoid()is a compile error (cannot assign void result) - Only
<-go fnRetVoid()is allowed to wait for completion
2. Compilation
go fn(args...) compiles to a channel-returning form:
func() chan T {
c := make(chan T, 1)
go func() { c <- fn(args...) }()
return c
}()2.1 Return channel handle
fut := go fn(args...)Compiles to:
fut := func() chan T {
c := make(chan T, 1)
go func() { c <- fn(args...) }()
return c
}()Optimization: If fut is only used once with no intervening statements:
fut := go fn(args...)
x := <-futCan be optimized to: x := fn(args...)
Non-optimizable: If there are intervening statements (concurrency exists):
fut := go fn(args...)
doSomething() // Concurrent with fn()
x := <-fut // Cannot optimize2.2 Direct combination
x := <-go fn(args...)Compiles to:
x := <-func() chan T {
c := make(chan T, 1)
go func() { c <- fn(args...) }()
return c
}()Optimization: Can be optimized to: x := fn(args...)
Non-optimizable: In select statement (requires channel semantics):
select {
case x := <-go fn1(): // Cannot optimize, must generate channel
case y := <-go fn2():
}2.3 Wait for completion (no return value)
<-go fn(args...)Compiles to:
<-func() chan struct{} {
c := make(chan struct{}, 1)
go func() { fn(args...); c <- struct{}{} }()
return c
}()Optimization: Can be optimized to: fn(args...)
2.4 Multi-return values
fut := go split3() // func split3() (int, string, error)
a, b, err := <-futCompiles to:
type _gop_ret_split3 struct { _r0 int; _r1 string; _r2 error }
fut := func() chan _gop_ret_split3 {
c := make(chan _gop_ret_split3, 1)
go func() {
r0, r1, r2 := split3()
c <- _gop_ret_split3{r0, r1, r2}
}()
return c
}()
_tmp := <-fut
a, b, err := _tmp._r0, _tmp._r1, _tmp._r22.5 Fire-and-forget
go fn(args...)Compiles to:
go fn(args...) // Unchanged3. About llgo
Standard compiled form:
func() chan T {
c := make(chan T, 1)
go func() { c <- fn(args...) }()
return c
}()Requirements:
- Outer function is synchronous
gostatement initiates goroutine creation (non-async call)func() { c <- fn(args...) }()is colored as sync or async based onfn's type
Background
Currently, the go statement in Go (and by extension, XGo/llgo) serves as a "fire-and-forget" mechanism that does not return a result. To capture a return value from a concurrent task, developers must manually manage channels or shared memory, which introduces boilerplate and increases complexity.
A key motivation for this proposal is to fully leverage the new implicit asynchronous function capabilities in llgo. By extending the go statement into an expression that returns a chan T (acting as a Future/Promise handle), the language can provide a more seamless and idiomatic way to handle concurrency. This enables:
fut := go fn(args...): Starting a concurrent task and obtaining a result channel handle.x := <-fut: Waiting for and receiving the result from the handle.select { case x := <-go fn(): ... }: Direct integration of concurrent calls withinselectstatements.
Workarounds
Until the compiler natively supports the go expression syntax, the following patterns can be used to achieve equivalent logic manually. These also represent the proposed lowering path for the compiler:
1. Function Call with Return Value
Equivalent to x := <-go fn(args...).
x := <-func() chan T {
c := make(chan T, 1) // Buffered channel (size 1) avoids blocking the goroutine
go func() {
c <- fn(args...)
}()
return c
}()2. Function with Multiple Return Values
Multiple returns can be handled by wrapping them in a temporary structure:
type _ret_struct struct { r0 T1; r1 T2 }
fut := func() chan _ret_struct {
c := make(chan _ret_struct, 1)
go func() {
r0, r1 := fn()
c <- _ret_struct{r0, r1}
}()
return c
}()
res := <-fut
// Access values via res.r0, res.r13. No Return Value (Wait for Completion)
Equivalent to <-go fn(args...). Using chan struct{} to signal task completion:
<-func() chan struct{} {
c := make(chan struct{}, 1)
go func() {
fn()
c <- struct{}{}
}()
return c
}()