doc/func-closure.md
Functions in XGo are defined with clear type specifications for parameters and return values:
func add(x int, y int) int {
return x + y
}
echo add(2, 3) // 5
When multiple consecutive parameters share the same type, you can combine them for more concise syntax:
// Verbose form: each parameter has its own type
func add(x int, y int) int {
return x + y
}
// Concise form: parameters of the same type can be grouped
func add(x, y int) int {
return x + y
}
// Mixed types require separate declarations
func greet(firstName, lastName string, age int) {
echo firstName, lastName, "is", age, "years old"
}
The same grouping rule applies to return values:
// Multiple return values of the same type can be grouped
func minMax(a, b int) (min, max int) {
if a < b {
return a, b
}
return b, a
}
// Mixed return types require separate declarations
func divide(a, b float64) (result float64, err error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
Key points:
x, y int instead of x int, y intFunctions don't always need to return values. When a function performs an action without producing a result, you can omit the return type:
func greet(name string) {
echo "Hello,", name
}
greet("Alice") // Hello, Alice
You can also explicitly return early from such functions using a bare return statement:
func printPositive(x int) {
if x <= 0 {
return // exit early if condition not met
}
echo "Positive number:", x
}
printPositive(5) // Positive number: 5
printPositive(-3) // (prints nothing)
Functions can return multiple values simultaneously:
func foo() (int, int) {
return 2, 3
}
a, b := foo()
echo a // 2
echo b // 3
c, _ := foo() // ignore values using `_`
Return values can be named to simplify return statements:
func sum(a ...int) (total int) {
for x in a {
total += x
}
return // no need to explicitly return named values that are already assigned
}
echo sum(2, 3, 5) // 10
XGo supports optional parameters using the T? syntax. Optional parameters default to their type's zero value:
func greet(name string, count int?) {
if count == 0 {
count = 1
}
for i := 0; i < count; i++ {
echo "Hello,", name
}
}
greet "Alice", 3 // prints "Hello, Alice" three times
greet "Bob" // prints "Hello, Bob" once (uses default value)
Optional parameters are denoted by adding ? after the parameter type. The default value is always the zero value of that type (e.g., 0 for integers, "" for strings, false for booleans).
func connect(host string, port int?, secure bool?) {
if port == 0 {
port = 80
}
echo "Connecting to", host, "on port", port, "secure:", secure
}
connect "example.com", 443, true // Connecting to example.com on port 443 secure: true
connect "example.com" // Connecting to example.com on port 80 secure: false
Variadic parameters allow functions to accept a variable number of arguments of the same type using the ... syntax:
func sum(a ...int) int {
total := 0
for x in a {
total += x
}
return total
}
echo sum(2, 3, 5) // 10
echo sum(1, 2, 3, 4, 5) // 15
echo sum() // 0
Inside the function, the variadic parameter behaves as a slice of the specified type.
Important: The variadic parameter must be the last parameter in the function signature, appearing after all regular parameters and optional parameters:
// ✓ Correct: variadic parameter is last
func log(level string, verbose bool?, messages ...string) {
// ...
}
// ✗ Wrong: variadic parameter must be last
func log(messages ...string, level string) { // Error!
// ...
}
// ✗ Wrong: variadic parameter must be after optional parameters
func log(messages ...string, verbose bool?) { // Error!
// ...
}
func log(level string, messages ...string) {
echo "[" + level + "]", strings.Join(messages, " ")
}
log("INFO", "Server", "started", "successfully")
// Output: [INFO] Server started successfully
Use the ... suffix to pass an existing slice to a variadic parameter:
func max(nums ...int) int {
if len(nums) == 0 {
return 0
}
maxVal := nums[0]
for _, n in nums[1:] {
if n > maxVal {
maxVal = n
}
}
return maxVal
}
numbers := []int{3, 7, 2, 9, 1}
echo max(numbers...) // 9
echo max(5, 8, 3) // 8
// String formatting
func format(template string, args ...any) string {
result := template
for i, arg in args {
result = strings.Replace(result, "{${i}}", fmt.Sprint(arg), 1)
}
return result
}
echo format("Hello {0}, you have {1} messages", "Alice", 5)
// Output: Hello Alice, you have 5 messages
XGo supports keyword arguments syntax for improved code readability and expressiveness. When calling functions with many parameters, you can use key = value syntax.
func process(opts map[string]any?, args ...any) {
if name, ok := opts["name"]; ok {
echo "Name:", name
}
if age, ok := opts["age"]; ok {
echo "Age:", age
}
echo "Args:", args
}
process name = "Ken", age = 17 // keyword parameters only
process "extra", 1, name = "Ken", age = 17 // variadic parameters first, then keyword parameters
process // all parameters optional
Best for: Dynamic or extensible parameter sets, diverse parameter types, runtime parameter checking.
type Config (timeout, maxRetries int, debug bool)
func run(task int, cfg Config?) {
if cfg.timeout == 0 {
cfg.timeout = 30
}
if cfg.maxRetries == 0 {
cfg.maxRetries = 3
}
echo "timeout:", cfg.timeout, "maxRetries:", cfg.maxRetries, "debug:", cfg.debug
echo "task:", task
}
run 100, timeout = 60, maxRetries = 5
run 200
Best for: Fixed parameter sets with known types, compile-time validation, optimal performance. Recommended for most use cases.
Note: Tuple field names must match exactly as defined - no automatic case conversion is performed.
Structs provide type safety and full runtime reflection support for keyword parameters:
type Config struct {
Timeout int
MaxRetries int
Debug bool
}
func run(cfg *Config?) {
timeout := 30
maxRetries := 3
debug := false
if cfg != nil {
if cfg.Timeout > 0 {
timeout = cfg.Timeout
}
if cfg.MaxRetries > 0 {
maxRetries = cfg.MaxRetries
}
debug = cfg.Debug
}
echo "Timeout:", timeout, "MaxRetries:", maxRetries, "Debug:", debug
}
run timeout = 60, maxRetries = 5 // lowercase field names work
run Timeout = 10, Debug = true // uppercase field names work too
run // uses default values
Best for: Go codebase compatibility, struct features (methods, embedding, tags), runtime reflection needs.
Syntax Rules:
Parameter Position Requirements
?)Call Order Requirements
// ✓ Correct call order
process "value", key1 = "a", key2 = "b"
process "v1", "v2", key = "x"
// ✗ Wrong call order
process key = "x", "value" // keyword arguments must come last
Type Selection:
Functions can be passed as parameters to other functions:
func square(x float64) float64 {
return x*x
}
func abs(x float64) float64 {
if x < 0 {
return -x
}
return x
}
func transform(a []float64, f func(float64) float64) []float64 {
return [f(x) for x in a]
}
y := transform([1, 2, 3], square)
echo y // [1 4 9]
z := transform([-3, 1, -5], abs)
echo z // [3 1 5]
Lambda expressions provide a concise way to define anonymous functions inline using the => operator.
// Single parameter (no parentheses needed)
x => x * x
// Multiple parameters (parentheses required)
(x, y) => x + y
// No parameters (no parentheses needed, just start with =>)
=> someValue
// Multi-line body
x => {
result := x * 2
return result
}
Transformations:
func transform(a []float64, f func(float64) float64) []float64 {
return [f(x) for x in a]
}
// The lambda parameter type is inferred from transform's function parameter type
// which expects func(float64) float64
y := transform([1, 2, 3], x => x*x) // [1 4 9]
z := transform([-3, 1, -5], x => {
if x < 0 {
return -x
}
return x
}) // [3 1 5]
Combining values:
func combine(a, b []int, f func(int, int) int) (result []int) {
for i := 0; i < len(a) && i < len(b); i++ {
result = append(result, f(a[i], b[i]))
}
return result
}
sums := combine([1, 2, 3], [4, 5, 6], (x, y) => x + y) // [5 7 9]
Closures (capturing variables):
func multiplier(factor int) func(int) int {
return x => x * factor
}
func counter(start int) func() int {
count := start
return => {
count++
return count
}
}
double := multiplier(2)
echo double(5) // 10
c := counter(0)
echo c() // 1
echo c() // 2
Sorting and filtering:
numbers := [1, 2, 3, 4, 5, 6]
evens := filter(numbers, x => x % 2 == 0) // [2 4 6]
sort.Slice(products, (i, j) => products[i].Price < products[j].Price)
Event handling:
// Event registration functions
func OnStart(onStart func())
func OnMsg(msg string, onMsg func())
// Register event handlers with lambdas
// With one argument (the lambda), no comma is needed.
onStart => {
echo "Game started!"
initializeGame()
}
// With multiple arguments, use a comma to separate them.
onMsg "game over", => {
echo "Game over!"
cleanup()
}
Note: If a function with the lowercase name doesn't exist, XGo will automatically look for the capitalized version (e.g., if onStart is not defined, it tries OnStart). This allows for more flexible and natural function calling syntax.
Parameter and return types are automatically inferred from context. XGo lambdas do not support explicit type annotations:
// Types are inferred from the function signature
transform([1, 2, 3], x => x * 2)
// The lambda parameter type is inferred from transform's function parameter type
// which expects func(float64) float64
Use lambdas for:
Use named functions for:
XGo's function system combines powerful features:
These features work together to create a versatile and expressive function system that supports both traditional imperative programming and modern functional programming paradigms.