O que são Scope Functions em Kotlin?

Scope Functions são cinco funções da biblioteca padrão do Kotlin — let, run, with, apply e also — que permitem executar um bloco de código no contexto de um objeto. Elas deixam o código mais conciso e expressivo.

A diferença entre elas está em como o objeto é referenciado dentro do bloco (this ou it) e no que cada uma retorna.

Resumo rápido

FunçãoReferênciaRetorno
letitResultado do lambda
runthisResultado do lambda
withthisResultado do lambda
applythisO próprio objeto
alsoitO próprio objeto

let — transformações e null safety

val nome: String? = "Kotlin Brasil"
nome?.let {
    println("O nome tem ${it.length} caracteres")
}

Perfeito pra trabalhar com valores nullable de forma segura.

apply — configurar objetos

data class Servidor(var host: String = "", var porta: Int = 0, var protocolo: String = "")

val servidor = Servidor().apply {
    host = "api.kotlin.dev.br"
    porta = 443
    protocolo = "HTTPS"
}
println(servidor)

O apply retorna o próprio objeto, ideal pra configurar e já usar em seguida.

also — ações laterais

val numeros = mutableListOf(1, 2, 3)
    .also { println("Lista original: $it") }
    .also { it.add(4) }

println(numeros) // [1, 2, 3, 4]

Bom pra logging e debug sem quebrar a cadeia de chamadas.

run — calcular resultado

val resultado = "Kotlin".run {
    println("Processando: $this")
    length * 2
}
println(resultado) // 12

with — operar sobre um objeto

val sb = StringBuilder()
val texto = with(sb) {
    append("Olá, ")
    append("mundo!")
    toString()
}
println(texto) // Olá, mundo!

Combinando scope functions

Na prática, você frequentemente combina scope functions para criar código fluente e expressivo. Veja um exemplo de configuração de uma requisição HTTP:

data class Requisicao(
    var url: String = "",
    var metodo: String = "GET",
    var headers: MutableMap<String, String> = mutableMapOf(),
    var corpo: String? = null
)

fun criarRequisicao(): Requisicao {
    return Requisicao().apply {
        url = "https://api.kotlin.dev.br/usuarios"
        metodo = "POST"
        headers["Content-Type"] = "application/json"
        headers["Authorization"] = "Bearer token123"
        corpo = """{"nome": "Karina"}"""
    }.also {
        println("Requisição criada: ${it.metodo} ${it.url}")
    }
}

run sem objeto — executando blocos

O run também pode ser usado sem um objeto receptor, funcionando como um bloco de código que retorna um valor:

val resultado = run {
    val a = 10
    val b = 20
    val c = 30
    a + b + c
}
println(resultado) // 60

Isso é útil quando você precisa calcular um valor complexo em uma variável val, criando um escopo isolado para variáveis temporárias.

let para encadeamento de transformações

O let brilha quando você precisa transformar um valor em cadeia:

fun processarEntrada(entrada: String?): String {
    return entrada
        ?.let { it.trim() }
        ?.let { it.lowercase() }
        ?.let { it.replace(" ", "_") }
        ?.let { "usuario_$it" }
        ?: "usuario_anonimo"
}

fun main() {
    println(processarEntrada("  Kotlin Brasil  ")) // usuario_kotlin_brasil
    println(processarEntrada(null))                  // usuario_anonimo
}

Qual usar?

Na dúvida: apply pra configurar, let pra null safety, also pra ações laterais, run pra calcular algo e with quando já tem o objeto em mãos.

Casos de Uso no Mundo Real

  • Configuração de objetos complexos: usar apply para configurar ViewModels, clientes HTTP, builders de notificação e outros objetos com múltiplas propriedades no Android e backend.
  • Null safety em respostas de API: usar let combinado com ?. para processar dados que podem ser nulos vindos de APIs REST ou banco de dados, evitando verificações manuais de null.
  • Logging e debug em pipelines: inserir also no meio de cadeias de chamadas para imprimir valores intermediários sem alterar o fluxo de dados, facilitando a depuração.
  • Inicialização de recursos com run: usar run para criar e configurar recursos como conexões de banco de dados, onde o resultado da configuração é o valor retornado.

Boas Práticas

  • Não aninhe scope functions. objeto.let { it.apply { also { } } } é ilegível. Limite-se a um nível de profundidade.
  • Use apply para configuração e also para efeitos colaterais. Embora ambos retornem o próprio objeto, a distinção semântica mantém o código claro.
  • Prefira let com nome explícito para lambdas longas: valor?.let { usuário -> ... } é mais legível que valor?.let { it.nome; it.email; ... } quando o bloco é grande.
  • Use with apenas quando o objeto não é nullable. Como with não é uma função de extensão, ele não funciona com o operador ?..
  • Mantenha blocos de scope functions curtos — entre 3 e 7 linhas. Se o bloco crescer muito, extraia para uma função separada.

Erros Comuns

  • Aninhar scope functions excessivamente: a.let { it.run { this.apply { } } } cria código impossível de entender. Se precisa de mais de uma scope function, quebre em etapas.
  • Confundir this e it: apply e run usam this (receptor implícito); let e also usam it (parâmetro explícito). Usar o errado gera confusão e erros de compilação.
  • Usar let sem necessidade: valor.let { println(it) } não é melhor que println(valor). Use let quando há uma razão real, como null safety ou transformação.
  • Ignorar o retorno: apply e also retornam o objeto; let, run e with retornam o resultado do lambda. Confundir isso leva a bugs sutis, especialmente em cadeias de chamadas.
  • Usar with em objetos nullable: como with não é uma extensão, with(objetoNullable) compila mas pode dar NullPointerException. Para nullable, prefira objetoNullable?.run { }.

Perguntas Frequentes

Qual a diferença entre let e run? Ambos retornam o resultado do lambda. A diferença é que let referência o objeto como it (parâmetro), enquanto run referência como this (receptor). Use let quando precisar de um nome explícito para o objeto ou para null safety; use run quando quiser acessar propriedades e métodos diretamente.

Posso substituir todas as scope functions por if/else e variáveis temporárias? Sim, scope functions são açúcar sintático. Mas elas tornam o código mais idiomático e expressivo. A comunidade Kotlin espera ver scope functions usadas nos contextos apropriados.

Scope functions afetam a performance? Na prática, não. As scope functions let, run, with, apply e also são todas inline, ou seja, não criam objetos lambda em tempo de execução. O compilador insere o código diretamente no ponto de chamada.

Quando usar with em vez de run? Use with quando já tem o objeto em uma variável e quer operar sobre ele sem encadear. Use run quando quer encadear a chamada com ?. (null safety) ou quando o objeto é resultado de outra expressão.

Termos Relacionados

  • Lambda — expressões lambda que formam o bloco de código das scope functions
  • Extension Functionlet, run, apply e also são funções de extensão
  • Nullable — scope functions como let são fundamentais para null safety
  • Inline Function — scope functions são inline, sem overhead de runtime
  • Higher-Order Function — scope functions são funções de ordem superior