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ção | Referência | Retorno |
|---|---|---|
let | it | Resultado do lambda |
run | this | Resultado do lambda |
with | this | Resultado do lambda |
apply | this | O próprio objeto |
also | it | O 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
applypara 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
letcombinado 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
alsono 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: usarrunpara 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
applypara configuração ealsopara efeitos colaterais. Embora ambos retornem o próprio objeto, a distinção semântica mantém o código claro. - Prefira
letcom nome explícito para lambdas longas:valor?.let { usuário -> ... }é mais legível quevalor?.let { it.nome; it.email; ... }quando o bloco é grande. - Use
withapenas quando o objeto não é nullable. Comowithnã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
thiseit:applyerunusamthis(receptor implícito);letealsousamit(parâmetro explícito). Usar o errado gera confusão e erros de compilação. - Usar
letsem necessidade:valor.let { println(it) }não é melhor queprintln(valor). Useletquando há uma razão real, como null safety ou transformação. - Ignorar o retorno:
applyealsoretornam o objeto;let,runewithretornam o resultado do lambda. Confundir isso leva a bugs sutis, especialmente em cadeias de chamadas. - Usar
withem objetos nullable: comowithnão é uma extensão,with(objetoNullable)compila mas pode darNullPointerException. Para nullable, prefiraobjetoNullable?.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 Function —
let,run,applyealsosão funções de extensão - Nullable — scope functions como
letsã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