GALA-E0015 — Bare return inside a value-producing match

What it means. A match expression is used as a value — passed as an argument, used as an operand, or returned as an expression — and one of its branches ends with a bare return (a return with no value). A match a local val or var is initialized with, or a variable is assigned, is not such a value: it is lowered as statements, and a bare return in it leaves the enclosing function. That is fine when the function returns nothing; when it returns a value, a bare return there is GALA-E0015 too — give the return a value.


Code that triggers it

package main

import "os"

func run(path string) {
    Println(Try(os.ReadFile(path)) match {
        case Success(b)   => string(b)
        case Failure(err) => {
            Println(s"error: ${err.Error()}")
            return                     // bare return in a value-producing branch
        }
    })
}

Compiler message

error[GALA-E0015]: bare `return` inside a match branch whose result is used as a value
  --> main.gala:6:13
  |
6 |     Println(Try(os.ReadFile(path)) match {
  |             ^^^ the match is wrapped in a function that must return string
  |
  = hint: the match is wrapped in a function that must return string; in a function that returns nothing, initialize a `val` with the match first (a `return` in it then leaves the function), restructure to early-exit before the match, or use combinators like .Recover / .GetOrElse. See docs/errors/GALA-E0015.md

The caret marks the match being used as a value, and the hint names the result type its wrapper must produce.


Why the generated Go cannot work

A match-as-value lowers to an immediately-invoked function expression:

fmt.Println(func() string {
    switch ... {
    case Success: return string(b)
    case Failure: fmt.Println(...); return   // invalid: must return string
    }
}(...))

A bare return there does not exit the enclosing function — it exits the wrapper. Because the wrapper has a non-void return type, Go rejects it with “not enough return values”.


How to fix it

1. Initialize a val with the match. The match is then lowered as statements, and the bare return leaves run:

func run(path string) {
    val data = Try(os.ReadFile(path)) match {
        case Success(b)   => string(b)
        case Failure(err) => {
            Println(s"error: ${err.Error()}")
            return
        }
    }
    Println(data)
}

2. Early-exit before the match. Handle the failure first, then destructure the success case:

func run(path string) {
    val result = Try(os.ReadFile(path))
    if (result.IsFailure()) {
        Println(s"error: ${result.GetError().Error()}")
        return
    }
    Println(string(result.Get()))
}

3. Use combinators. Map, Recover, and GetOrElse avoid the wrapper entirely:

func run(path string) {
    val data = Try(os.ReadFile(path))
        .Map((b) => string(b))
        .GetOrElse("")
    if (data == "") { return }
    Println(data)
}

4. Make the match the function body. When the match is the function’s result, each branch legitimately returns a value.


Why the rule exists

GALA’s match-as-value form has expression semantics: every branch must contribute a value of the match’s result type. A bare return is a statement-level control-flow operation whose scope — outer function or enclosing wrapper — is ambiguous where the match is lowered to a wrapper; a match a val is initialized with is lowered as statements instead. Rather than silently pick one reading and generate surprising Go, the transpiler rejects the construct and points at the explicit rewrites.