A palavra-chave func declara todo o comportamento de um programa Go. func parse(body []byte) (Request, error) é uma função. func (s *Server) Start() error é um método, porque tem um recetor antes do nome. func(w http.ResponseWriter, r *http.Request) { ... } sem nome é um literal de função, que pode capturar as variáveis à sua volta e é a forma como o Go escreve closures. Uma só palavra-chave cobre os três casos, e as regras para parâmetros e resultados são as mesmas em todos (especificação do Go).
Resumo rápido
- Uma declaração de função é
func name(params) results { body }. Parâmetros consecutivos do mesmo tipo partilham-no, como emclamp(n, lo, hi int). - O Go não tem sobrecarga nem argumentos por omissão. Duas funções com o mesmo nome falham com
redeclared in this block. Usa outro nome, uma struct de opções ou functional options. - As funções podem devolver vários valores. O par
(T, error)é a forma como o Go comunica uma falha, e o_descarta um resultado de que não precisas. - Os resultados com nome documentam o que uma função devolve e deixam uma closure diferida anotar o
errà saída. ...Ttorna uma função variádica. Lá dentro, o parâmetro é um[]T, eparts...espalha um slice pelos argumentos da chamada.- Um método é uma função com um recetor. Usa um recetor ponteiro quando o método altera o valor ou quando o tipo contém um
sync.Mutex. - Um método com recetor ponteiro não faz parte do conjunto de métodos do tipo valor, por isso
API{}não satisfazhttp.Handlerquando só o*APItemServeHTTP. - As funções são valores. Podes guardá-las, passá-las ao
slices.SortFuncou aohttp.HandleFunc, e só as podes comparar comnil. - Um literal de função captura as próprias variáveis, não cópias. Desde o Go 1.22, cada iteração de um ciclo tem a sua própria variável de ciclo, por isso as goroutines lançadas num ciclo veem o valor que esperam.
- As functional options (
NewServer(addr, opts ...Option)) dão configurações opcionais aos construtores. Desde o Go 1.27, os métodos podem declarar os seus próprios parâmetros de tipo, como emfunc (c *Client) GetJSON[T any](path string) (T, error).
Como se declara uma função em Go?
Escreve func, um nome, a lista de parâmetros e os tipos dos resultados. Um parser de tokens é um bom exemplo de função pequena:
example.gogopackage main import ( "errors" "fmt" "strings" ) func parseBearer(header string) (string, error) { token, ok := strings.CutPrefix(header, "Bearer ") if !ok || token == "" { return "", errors.New("missing bearer token") } return token, nil } func clamp(n, lo, hi int) int { return max(lo, min(n, hi)) } func main() { token, err := parseBearer("Bearer eyJhbGciOi") fmt.Println(token, err) _, err = parseBearer("Basic dXNlcjpwYXNz") fmt.Println(err) fmt.Println(clamp(250, 1, 100)) }
example.texttexteyJhbGciOi <nil> missing bearer token 100
Os tipos vêm depois dos nomes, tanto nos parâmetros como nos resultados. Quando vários parâmetros seguidos têm o mesmo tipo, escreves o tipo uma só vez, por isso clamp(n, lo, hi int) recebe três int. Um único resultado sem nome dispensa parênteses. Dois ou mais resultados precisam deles.
Os nomes de funções seguem a mesma regra de visibilidade que qualquer outro identificador Go. ParseBearer seria exportada e poderia ser chamada a partir de outros pacotes, enquanto parseBearer fica dentro do seu. A palavra-chave package em Go explica os nomes exportados com mais detalhe.
O Go tem sobrecarga de funções ou argumentos por omissão?
Nenhum dos dois. Um pacote só pode ter uma função com um dado nome, sejam quais forem os parâmetros:
example.gogofunc dial(addr string) error { return nil } func dial(addr string, timeout time.Duration) error { return nil }
example.texttext./main.go:7:6: dial redeclared in this block ./main.go:5:6: other declaration of dial
Os valores por omissão falham logo no parser, antes de a verificação de tipos sequer começar:
example.texttext./main.go:5:46: syntax error: unexpected = in parameter list; possibly missing comma or )
A biblioteca padrão mostra o que o Go faz em vez disso. net.Dial e net.DialTimeout são dois nomes para dois comportamentos. O http.Server é uma struct cujos campos a zero significam «usa o valor por omissão», por isso só defines os campos que te interessam. Para construtores com muitas configurações opcionais, o código Go usa functional options, que aparecem mais abaixo. O nome em cada chamada diz-te que função é executada.
Como funcionam os múltiplos valores de retorno em Go?
Uma função pode devolver qualquer número de valores, e quem a chama recebe-os com uma atribuição múltipla. A forma mais comum é um resultado mais um error, como no parseBearer acima. Quem chama verifica o erro antes de mexer no resultado, e o compilador garante que uma chamada com dois resultados não é usada onde se espera um único valor.
Quando não precisas de um dos valores, atribui-o ao identificador em branco _. Não o podes simplesmente omitir. token := parseBearer(h) falha com assignment mismatch: 1 variable but parseBearer returns 2 values.
Quando deves usar parâmetros de resultado com nome?
Os resultados com nome dão um nome a cada resultado e declaram-no como uma variável que começa no seu valor zero. Um return sem valores, o chamado bare return, devolve então o que essas variáveis tiverem nesse momento:
example.gogofunc splitHostPort(addr string) (host, port string) { for i := len(addr) - 1; i >= 0; i-- { if addr[i] == ':' { host, port = addr[:i], addr[i+1:] return } } host = addr return }
splitHostPort("api.levelupgo.dev:443") devolve api.levelupgo.dev e 443. Os nomes ajudam aqui porque, sem eles, dois resultados string não diriam qual é qual. Os Go Code Review Comments sugerem bare returns apenas em funções curtas como esta. Numa função de 60 linhas, um return sem valores esconde o que está a ser devolvido.
Os resultados com nome fazem mais diferença quando uma função diferida precisa de editar o erro antes de quem chama o ver:
example.gogofunc loadConfig(path string) (cfg Config, err error) { defer func() { if err != nil { err = fmt.Errorf("load config %s: %w", path, err) } }() data, err := os.ReadFile(path) if err != nil { return Config{}, err } err = json.Unmarshal(data, &cfg) return cfg, err }
example.texttextload config /etc/levelupgo/config.json: open /etc/levelupgo/config.json: no such file or directory
Cada return atribui primeiro os seus valores a cfg e err, e só depois é executada a função diferida. Como err é um resultado com nome, a closure pode embrulhá-lo uma só vez para todos os caminhos de saída. Sem o nome, a função diferida não tem forma de chegar ao valor que está a ser devolvido.
O que são funções variádicas em Go?
Um último parâmetro escrito como ...T aceita zero ou mais argumentos do tipo T. Dentro da função, é um simples []T:
example.gogofunc buildURL(base string, segments ...string) string { return strings.TrimSuffix(base, "/") + "/" + strings.Join(segments, "/") } func main() { fmt.Println(buildURL("https://api.levelupgo.dev/", "v1", "users", "42")) fmt.Println(buildURL("https://api.levelupgo.dev")) parts := []string{"v1", "orders", "1001"} fmt.Println(buildURL("https://api.levelupgo.dev", parts...)) }
example.texttexthttps://api.levelupgo.dev/v1/users/42 https://api.levelupgo.dev/ https://api.levelupgo.dev/v1/orders/1001
parts... passa um slice existente como argumento variádico sem o copiar. Não podes misturar as duas formas, por isso buildURL(base, "v2", parts...) falha:
example.texttext./main.go:18:58: too many arguments in call to buildURL have (string, string, []string...) want (string, ...string)
A biblioteca padrão está cheia delas. fmt.Println(a ...any), append(s, elems...), errors.Join(errs ...error) e os pares chave-valor em slog.Info(msg, args ...any) seguem todos esta forma.
Como funcionam os métodos em Go?
Um método é uma função com um recetor, escrito entre parênteses entre func e o nome. Na maioria das vezes, os recetores são structs, que a palavra-chave struct em Go explica dos campos à incorporação. O recetor pode ser o próprio tipo ou um ponteiro para ele, e essa escolha decide se o método trabalha sobre o valor de quem chama ou sobre uma cópia. Um rate limiter com um recetor valor mostra a diferença:
example.gogotype RateLimiter struct { limit int used int } func (r RateLimiter) Allow() bool { if r.used >= r.limit { return false } r.used++ return true } func main() { limiter := RateLimiter{limit: 2} for range 4 { fmt.Print(limiter.Allow(), " ") } fmt.Println() }
example.texttexttrue true true true
O limiter nunca bloqueia nada. Cada chamada recebe uma cópia nova de limiter, incrementa used na cópia e deita-a fora. Mudar o recetor para func (r *RateLimiter) Allow() bool faz com que o método atualize o original, e o output passa a ser true true false false. Não precisas de escrever (&limiter).Allow(). O Go obtém o endereço por ti quando a variável é endereçável.
Quando deves usar um recetor ponteiro?
Usa um recetor ponteiro quando se verifica qualquer uma destas condições:
- O método altera o recetor, como o
Allowacima. - O tipo contém um
sync.Mutexou outro valor que não pode ser copiado. - A struct é grande, por isso copiá-la em cada chamada custa mais do que seguir um ponteiro.
Recetor valor func (r T) | Recetor ponteiro func (r *T) | |
|---|---|---|
| Altera o valor de quem chama | Não, trabalha sobre uma cópia | Sim |
Seguro com um campo sync.Mutex | Não, o lock é copiado | Sim |
Pode ser chamado numa variável T | Sim | Sim, o Go obtém o endereço |
No conjunto de métodos de T | Sim | Não |
No conjunto de métodos de *T | Sim | Sim |
O caso do mutex é um bug que o go vet apanha:
example.gogotype SessionCache struct { mu sync.Mutex sessions map[string]string } func (c SessionCache) Get(token string) (string, bool) { c.mu.Lock() defer c.mu.Unlock() user, ok := c.sessions[token] return user, ok }
example.texttextmain.go:13:9: Get passes lock by value: app.SessionCache contains sync.Mutex
Cada chamada bloqueia a sua própria cópia do mutex, por isso duas goroutines que chamam Get nunca esperam uma pela outra e o lock não protege nada. Tipos pequenos e imutáveis, como time.Time ou um UserID, funcionam bem com recetores valor. Os Code Review Comments também pedem que não mistures os dois no mesmo tipo. Se um método precisa de um recetor ponteiro, dá um recetor ponteiro a todos (wiki do Go).
Porque é que o meu tipo não implementa uma interface?
Uma causa comum é um recetor ponteiro. O conjunto de métodos de um tipo valor T inclui apenas os métodos com recetor T. O conjunto de métodos de *T inclui os dois tipos de métodos. Por isso, quando o ServeHTTP tem um recetor ponteiro, só o *API é um http.Handler:
example.gogotype API struct { version string } func (a *API) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, a.version) } func main() { var h http.Handler = API{version: "v1"} _ = h }
example.texttext./main.go:17:23: cannot use API{…} (value of struct type API) as http.Handler value in variable declaration: API does not implement http.Handler (method ServeHTTP has pointer receiver)
Escrever &API{version: "v1"} resolve o problema. A regra existe porque uma interface pode guardar uma cópia de um valor, e um método com recetor ponteiro chamado sobre essa cópia alteraria algo que quem chama nunca vê. A palavra-chave var em Go mostra o padrão var _ http.Handler = (*T)(nil), que verifica isto em tempo de compilação, e a palavra-chave interface em Go aprofunda os conjuntos de métodos e a satisfação implícita.
O que são method values e method expressions?
srv.health sem parênteses é um method value. É uma função com o recetor já associado, por isso podes passá-la diretamente a qualquer coisa que espere uma função simples:
example.gogotype Server struct { version string } func (s *Server) health(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "ok %s\n", s.version) } func main() { srv := &Server{version: "1.4.2"} mux := http.NewServeMux() mux.HandleFunc("GET /health", srv.health) rec := httptest.NewRecorder() mux.ServeHTTP(rec, httptest.NewRequest("GET", "/health", nil)) fmt.Print(rec.Body.String()) handle := (*Server).health fmt.Printf("%T\n", handle) }
example.texttextok 1.4.2 func(*main.Server, http.ResponseWriter, *http.Request)
Muitos servidores Go registam as rotas desta forma. Os handlers são métodos numa struct que guarda a base de dados, o logger e a configuração, e o mux.HandleFunc recebe os method values já associados. (*Server).health é uma method expression. Transforma o recetor num primeiro parâmetro comum, o que dá jeito quando uma tabela escolhe em tempo de execução que método chamar.
O que são valores de função e tipos função?
As funções são valores em Go. Podes atribuí-las a variáveis, guardá-las em campos de structs e em maps, passá-las como argumentos e devolvê-las a partir de outras funções. O tipo de um valor de função é a sua assinatura:
example.gogotype Order struct { ID int64 Total int64 } func main() { orders := []Order{{ID: 1, Total: 4999}, {ID: 2, Total: 1250}, {ID: 3, Total: 8900}} slices.SortFunc(orders, func(a, b Order) int { return cmp.Compare(b.Total, a.Total) }) fmt.Println(orders) bigOrder := func(o Order) bool { return o.Total > 5000 } fmt.Println(slices.IndexFunc(orders, bigOrder)) fmt.Printf("%T\n", bigOrder) }
example.texttext[{3 8900} {1 4999} {2 1250}] 0 func(main.Order) bool
O slices.SortFunc não sabe como ordenar as tuas structs, por isso passas-lhe uma função de comparação. A mesma ideia aparece em toda a biblioteca padrão: o http.HandleFunc recebe uma função handler, o strings.FieldsFunc recebe um teste de separador e o sync.OnceValue recebe a função a executar uma única vez.
O valor zero de um tipo função é nil, e chamar uma função nil provoca um panic. É também a única comparação que o Go permite:
example.texttext./main.go:9:5: invalid operation: health == ready (func can only be compared to nil)
Como não podes comparar duas funções, também não as podes usar como chaves de um map. Quando queres um conjunto de handlers, usa o nome como chave do map.
Uma assinatura também pode ter um nome próprio com type, e um tipo função com nome pode ter métodos. É assim que o http.HandlerFunc transforma uma função simples num http.Handler, e a palavra-chave type em Go explica isso passo a passo, juntamente com um exemplo de RetryPolicy.
Como funcionam as closures em Go?
Um literal de função pode usar variáveis da função que o envolve. Não as copia. Captura as próprias variáveis, por isso as alterações feitas de qualquer um dos lados são visíveis para ambos. Uma função auxiliar de retry mostra isto:
example.gogovar errUnavailable = errors.New("503 service unavailable") func retry(attempts int, fn func() error) error { var err error for range attempts { if err = fn(); err == nil { return nil } } return err } func main() { calls := 0 err := retry(3, func() error { calls++ return errUnavailable }) fmt.Println(calls, err) }
example.texttext3 503 service unavailable
calls vive em main, mas é o literal que o incrementa, e o main vê 3 no fim. A variável capturada mantém-se viva enquanto houver alguma closure que a referencie, mesmo depois de a função envolvente terminar. O compilador move essas variáveis para o heap quando precisa.
O middleware HTTP é uma das closures mais comuns em código Go. O handler devolvido guarda o logger e o next da chamada que o construiu:
example.gogofunc logRequests(logger *slog.Logger, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { next.ServeHTTP(w, r) logger.Info("request", "method", r.Method, "path", r.URL.Path) }) }
Embrulhar um handler e enviar-lhe DELETE /sessions/42 regista level=INFO msg=request method=DELETE path=/sessions/42. Cada chamada a logRequests produz uma closure separada com o seu próprio next, por isso podes embrulhar cada rota com um handler diferente. Para veres a pilha de produção completa construída com closures como esta, incluindo a ordem, a recuperação de panics e os testes, consulta boas práticas de middleware em Go.
Também vais encontrar muitas vezes duas formas curtas. defer func() { ... }() executa uma closure quando a função termina, e é assim que o exemplo loadConfig anota o seu erro e que se chama o recover. go func() { ... }() lança uma closure numa nova goroutine. Os () finais chamam o literal. Se os esqueceres, o compilador queixa-se de que a expressão no defer ou no go tem de ser uma chamada de função.
O Go 1.22 corrigiu as closures em ciclos?
Sim. Antes do Go 1.22, um ciclo for tinha uma única variável de ciclo partilhada por todas as iterações, e as closures capturavam essa variável. Este health checker lançava três goroutines que muitas vezes imprimiam todas o último URL:
example.gogourls := []string{"/health", "/ready", "/metrics"} var wg sync.WaitGroup for _, url := range urls { wg.Add(1) go func() { defer wg.Done() fmt.Println("checking", url) }() } wg.Wait()
Com go 1.21 no go.mod, todas as linhas de três execuções separadas imprimiram checking /metrics, e o go vet avisou loop variable url captured by func literal. Com go 1.22 ou posterior, cada iteração tem o seu próprio url, e o mesmo código verifica os três caminhos pela ordem em que as goroutines terminarem (blog do Go). A mudança depende da linha go no go.mod, por isso um módulo antigo mantém o comportamento antigo até atualizares essa linha. A velha cópia url := url dentro do ciclo deixou de ser necessária, e o modernizer forvar do go fix remove-a. A palavra-chave for em Go cobre as restantes regras das variáveis de ciclo, como a razão por que alterar o valor do range não altera o slice.
O que são functional options em Go?
As functional options combinam parâmetros variádicos e closures para dar configurações opcionais a um construtor sem sobrecarga nem argumentos por omissão. Cada opção é uma função que altera o valor em construção:
example.gogotype Server struct { addr string readTimeout time.Duration maxBodyBytes int64 logger *slog.Logger } type Option func(*Server) func WithReadTimeout(d time.Duration) Option { return func(s *Server) { s.readTimeout = d } } func WithMaxBodyBytes(n int64) Option { return func(s *Server) { s.maxBodyBytes = n } } func NewServer(addr string, opts ...Option) *Server { s := &Server{ addr: addr, readTimeout: 5 * time.Second, maxBodyBytes: 1 << 20, logger: slog.Default(), } for _, opt := range opts { opt(s) } return s } func main() { a := NewServer(":8080") b := NewServer(":8080", WithReadTimeout(30*time.Second), WithMaxBodyBytes(10<<20)) fmt.Println(a.readTimeout, a.maxBodyBytes) fmt.Println(b.readTimeout, b.maxBodyBytes) }
example.texttext5s 1048576 30s 10485760
NewServer(":8080") fica com todos os valores por omissão, e quem chama só indica o que muda. Acrescentar uma opção mais tarde não parte nenhuma chamada existente. O grpc.NewServer(opts ...ServerOption) do gRPC e muitos clientes de bases de dados usam este padrão. Para um tipo com duas ou três configurações, uma struct de configuração normal é mais simples e mais fácil de ler, por isso recorre às opções quando a lista de configurações não para de crescer.
Como funcionam as funções genéricas em Go?
Uma função pode declarar parâmetros de tipo entre parênteses retos, entre o nome e os parâmetros. O Map funciona para qualquer tipo de entrada e de saída:
example.gogofunc Map[T, U any](items []T, fn func(T) U) []U { out := make([]U, 0, len(items)) for _, item := range items { out = append(out, fn(item)) } return out } emails := Map(users, func(u User) string { return u.Email })
O compilador infere T como User e U como string a partir dos argumentos, por isso a chamada dispensa os parênteses retos. Os pacotes slices, maps e cmp da biblioteca padrão são construídos com funções genéricas como esta.
Até ao Go 1.27, os métodos não podiam declarar os seus próprios parâmetros de tipo. Podiam usar os parâmetros de tipo do tipo do seu recetor, e nada mais. O Go 1.27 remove esse limite, por isso um cliente pode ter um método genérico que descodifica para o tipo que quem chama pedir (notas de lançamento do Go 1.27):
example.gogotype Client struct { baseURL string http *http.Client } func (c *Client) GetJSON[T any](path string) (T, error) { var out T resp, err := c.http.Get(c.baseURL + path) if err != nil { return out, err } defer resp.Body.Close() err = json.NewDecoder(resp.Body).Decode(&out) return out, err } u, err := c.GetJSON[User]("/users/1")
Com um servidor de teste que devolve um utilizador, isto imprime {1 [email protected]} <nil>. Aqui o argumento de tipo tem de ser escrito explicitamente, porque nada em "/users/1" diz ao compilador o que é o T. As novidades do Go 1.27 explicam os métodos genéricos e os seus limites, incluindo porque é que não podem satisfazer métodos de interfaces.
O que são o main e o init em Go?
Há dois nomes de funções especiais. O func main() no package main é onde um programa começa, e o programa termina quando essa função retorna. Não recebe argumentos e não devolve nada:
example.texttext./main.go:9:6: func main must have no arguments and no return values
Os argumentos da linha de comandos vêm de os.Args ou do pacote flag, e o código de saída vem de os.Exit. A palavra-chave package em Go aprofunda o package main.
O func init() é executado automaticamente depois de as variáveis do pacote serem inicializadas e antes do main. Um pacote pode ter várias funções init, mesmo num só ficheiro, e são executadas pela ordem em que aparecem. Não podes chamar uma diretamente. init() no teu próprio código falha com undefined: init. A maior parte dos usos do init são registos, como um driver de base de dados que se regista no database/sql. A palavra-chave var em Go explica a ordem de inicialização e quando uma var ao nível do pacote é a opção mais clara.
Onde entra o LevelUpGo
O LevelUpGo ensina Go através de exercícios que executam código Go real no navegador. O Go Basics apresenta as funções, os múltiplos valores de retorno e os erros como valores. O Go Language Deep Dives vai mais longe nas funções variádicas, nos resultados com nome, no panic e no recover. O Training Ground tem exercícios curtos e independentes sobre assinaturas, métodos e closures para praticares fora de um curso. Para as outras 24 palavras reservadas, vê Palavras-chave do Go: as 25 explicadas.
Perguntas frequentes
O func é uma palavra-chave em Go?
Sim. O func é uma das 25 palavras-chave reservadas do Go, por isso não o podes usar como nome. Declara funções e métodos e inicia literais de função e tipos função. Em código Go, uma variável que guarda uma função costuma chamar-se fn.
O Go suporta sobrecarga de funções ou parâmetros por omissão?
Nenhum dos dois. Cada nome só pode ser declarado uma vez por pacote, por isso um segundo func dial(...) falha com dial redeclared in this block, e um valor por omissão numa lista de parâmetros é um erro de sintaxe. O Go usa nomes distintos, como net.Dial e net.DialTimeout, uma struct de configuração cujos campos a zero significam «usa o valor por omissão», como o http.Server, ou functional options.
Devo usar um recetor valor ou um recetor ponteiro em Go?
Usa um recetor ponteiro quando o método altera o recetor, quando o tipo contém um sync.Mutex ou algo semelhante, ou quando a struct é grande. Usa um recetor valor para tipos pequenos que se comportam como valores, como o time.Time. Mantém todos os métodos de um tipo consistentes. Lembra-te de que só o *T tem os métodos com recetor ponteiro no seu conjunto de métodos, o que importa para a satisfação de interfaces.
O que é o func() em Go?
O func() é o tipo de uma função que não recebe argumentos e não devolve nada. Qualquer assinatura escrita sem nome é um tipo função, como func(context.Context) error. Usas estes tipos para callbacks, campos de structs e parâmetros como sync.OnceFunc(f func()). func() { ... } com um corpo é um literal de função desse tipo.
Podes comparar funções em Go?
Só com nil. health == ready falha com func can only be compared to nil, e pela mesma razão um tipo função não pode ser chave de um map. Quando precisas de procurar handlers, guarda-os num map[string]http.HandlerFunc com o nome como chave.
Fontes
- The Go Programming Language Specification, Function declarations: https://go.dev/ref/spec#Function_declarations
- The Go Programming Language Specification, Method declarations: https://go.dev/ref/spec#Method_declarations
- The Go Programming Language Specification, Method sets: https://go.dev/ref/spec#Method_sets
- The Go Programming Language Specification, Function literals: https://go.dev/ref/spec#Function_literals
- The Go Programming Language Specification, Passing arguments to ... parameters: https://go.dev/ref/spec#Passing_arguments_to_..._parameters
- Effective Go, Functions: https://go.dev/doc/effective_go#functions
- Go Code Review Comments, Named result parameters and Receiver type: https://go.dev/wiki/CodeReviewComments
- The Go Blog, Fixing For Loops in Go 1.22: https://go.dev/blog/loopvar-preview
- Go 1.27 Release Notes: https://go.dev/doc/go1.27
