Usando goto e label em Go

A instrução goto tem fama negativa. No tempo do BASIC programadores não tinham muitas opções e abusavam do comando goto e criaram códigos confusos. Linguagens modernas, como Go, usam goto com critério e melhoram a clareza do código.

Go permite que os comandos break e continue usem labels. Use labels para sair de loops aninhados com break dar continue em um loop específico. Usar break ou continue com labels equivale a usar goto.

Exemplo de uso de break com label

Neste exemplo, o comando break sai dos dois loops aninhados.

label:
for coluna := 10; coluna > 0; coluna-- {
    for linha := 10; linha > 0; linha-- {
        fmt.Println(linha, coluna)
        if linha == 5 {
            break label
        }
    }
}

Exemplo de uso de continue com label

Neste exemplo, o comando continue pula o loop interno e continua o loop externo.

label:
for coluna := 10; coluna > 0; coluna-- {
    for linha := 10; linha > 0; linha-- {
        fmt.Println(linha, coluna)
        if linha == 5 {
            continue label
        }
    }
}

Exemplo de uso de goto com label

Neste exemplo, o comando goto cria um loop infinito. O loop interrompe quando o canal messages recebe uma mensagem ou quando o tempo de 2 segundos expira.

func main() {
    timeout := time.After(2 * time.Second)

    messages := make(chan string)
    go func() { messages <- "ping" }()

loop:
    select {
    case <-timeout:
        fmt.Println("timeout")
        os.Exit(1)

    case m := <-messages:
        fmt.Println(m)
        goto loop
    }

}

Exemplo simples de goto com label

Neste exemplo, o comando goto pula instruções intermediárias.

func main() {
    fmt.Println("1")
    goto label
    fmt.Println("2") // esta linha nunca será executada
label:
    fmt.Println("3")
}

Exemplo de erro ao usar goto

Neste exemplo, o comando goto falha. O comando pula a declaração de uma variável, o que não é permitido.

fmt.Println("1")
goto label
var i = 1
fmt.Println("2", i)
label:
fmt.Println("3")

Veja os exemplos de uso de goto com Go.

Regras para usar labels

As regras para usar goto, continue e break em Go são:

  • O programa não pode saltar para fora do escopo da função.
  • O programa não pode saltar sobre a declaração de uma variável.
  • O comando break com label exige que a instrução imediatamente após o label seja um for, switch ou select.
  • O comando continue com label exige que a instrução imediatamente após o label seja um for.

Nada de errado com goto

O assembly não entende abstrações. O processador executa saltos para endereços de memória. O compilador converte estruturas de controle (if, for, goto etc.) em variações da instrução jmp que efetivamnte é um goto. O programador escolhe usar ou não os recursos mas ele esta lá quer ele queira ou não.

Bonus um padtão de uso de goto em C

Em linguagens que não possuem defer como C, o comando goto pode ser usado para liberar recursos antes de sair de uma função. Por exemplo, no lugar de simplesmente comandar um return na função, o programador comanda um salto goto para label em que ele libera os recursos e daí sai.

void funcao() {
    FILE *f = fopen("arquivo.txt", "r");
    if (f == NULL) {
        goto erro;
    }

    // código que usa o arquivo

    fclose(f);
    return;

erro:
    perror("Erro ao abrir o arquivo");
    exit(1);
}

Conclusão

O comando goto é uma ferramenta poderosa. Devemos usá-lo com critério e seguir boas práticas de programação mas não se restringir porque ouviu falar que goto é ruim. O comando goto é uma ferramenta útil e deve ser usada quando necessário.

Vídeo com a explicação do artigo.

Cesar Gimenes

Última modificação