O que e Receiver em Kotlin?
O receiver e o objeto sobre o qual uma funcao opera, acessivel dentro da funcao atraves da palavra-chave this. Em Kotlin, o conceito de receiver aparece em extension functions, lambdas with receiver e scope functions, sendo fundamental para a criacao de APIs fluentes e DSLs.
Em uma funcao normal, os parametros sao explicitamente nomeados. Com um receiver, o objeto esta implicito no escopo da funcao, permitindo acessar suas propriedades e metodos diretamente sem qualificacao.
Receiver em extension functions
O exemplo mais comum de receiver e em extension functions:
fun String.contarVogais(): Int {
// 'this' se refere ao String sobre o qual a funcao foi chamada
return this.count { it in "aeiouAEIOU" }
}
fun main() {
val texto = "Kotlin Brasil"
println(texto.contarVogais()) // 5
}
Aqui, String e o tipo do receiver. Dentro da funcao, this aponta para a instancia de String sobre a qual contarVogais() foi chamado. O this pode ser omitido:
fun String.contarVogais(): Int {
return count { it in "aeiouAEIOU" } // 'this' implicito
}
Lambda with receiver
Uma lambda with receiver e uma lambda que tem acesso ao receiver como this. A sintaxe do tipo e TipoReceiver.() -> TipoRetorno:
fun construirMensagem(builder: StringBuilder.() -> Unit): String {
val sb = StringBuilder()
sb.builder() // Chama a lambda com sb como receiver
return sb.toString()
}
fun main() {
val mensagem = construirMensagem {
// Dentro dessa lambda, 'this' e o StringBuilder
append("Ola, ")
append("Kotlin!")
appendLine()
append("Tudo bem?")
}
println(mensagem)
}
A lambda builder tem StringBuilder como receiver, entao dentro dela voce pode chamar append, appendLine e outros metodos diretamente, como se estivesse dentro de um metodo da classe.
Scope functions e receivers
As scope functions do Kotlin usam diferentes tipos de receiver:
data class Pessoa(var nome: String, var idade: Int)
fun main() {
val pessoa = Pessoa("Ana", 25)
// apply: receiver e 'this', retorna o objeto
pessoa.apply {
nome = "Ana Maria" // this.nome
idade = 26 // this.idade
}
// with: receiver e 'this', retorna o resultado da lambda
val descricao = with(pessoa) {
"$nome tem $idade anos" // this.nome, this.idade
}
// run: receiver e 'this', retorna o resultado da lambda
val resultado = pessoa.run {
"$nome ($idade)"
}
// let: parametro e 'it', nao usa receiver
pessoa.let { p ->
println("${p.nome} tem ${p.idade} anos")
}
// also: parametro e 'it', retorna o objeto
pessoa.also {
println("Pessoa: ${it.nome}")
}
}
apply, with e run usam lambda with receiver (this). let e also usam lambda com parametro (it).
Criando DSLs com receiver
O poder real dos receivers aparece na criacao de DSLs (Domain Specific Languages):
class HtmlBuilder {
private val elementos = mutableListOf<String>()
fun h1(texto: String) {
elementos.add("<h1>$texto</h1>")
}
fun p(texto: String) {
elementos.add("<p>$texto</p>")
}
fun ul(configurar: ListaBuilder.() -> Unit) {
val builder = ListaBuilder()
builder.configurar()
elementos.add(builder.construir())
}
fun construir(): String = elementos.joinToString("\n")
}
class ListaBuilder {
private val itens = mutableListOf<String>()
fun li(texto: String) {
itens.add("<li>$texto</li>")
}
fun construir(): String {
val conteudo = itens.joinToString("\n ")
return "<ul>\n $conteudo\n</ul>"
}
}
fun html(configurar: HtmlBuilder.() -> Unit): String {
val builder = HtmlBuilder()
builder.configurar()
return builder.construir()
}
fun main() {
val pagina = html {
h1("Kotlin Brasil")
p("Aprenda Kotlin em portugues.")
ul {
li("Coroutines")
li("Flow")
li("Compose")
}
}
println(pagina)
}
Cada bloco { } tem um receiver diferente, e os metodos disponiveis mudam conforme o contexto. Isso cria uma sintaxe que parece uma linguagem propria.
@DslMarker para controle de escopo
Sem controle, receivers de escopos externos podem vazar para escopos internos:
@DslMarker
annotation class HtmlDsl
@HtmlDsl
class HtmlBuilder { /* ... */ }
@HtmlDsl
class ListaBuilder { /* ... */ }
// Agora, dentro de ul { }, voce NAO pode acessar h1() ou p()
// Isso impede erros de escopo na DSL
O @DslMarker restringe o acesso a receivers de escopos externos, tornando a DSL mais segura e previsivel.
Receiver em funcoes de extensao de tipos genericos
fun <T> T.tambem(bloco: (T) -> Unit): T {
bloco(this)
return this
}
fun <T> T.executar(bloco: T.() -> Unit): T {
this.bloco()
return this
}
A diferenca entre (T) -> Unit (parametro) e T.() -> Unit (receiver) e sutil mas importante:
// Com parametro: acesso via 'it' ou nome do parametro
"texto".tambem { valor -> println(valor.length) }
// Com receiver: acesso via 'this' (implicito)
"texto".executar { println(length) }
Multiplos receivers
Kotlin permite que uma funcao tenha um receiver de extensao e um dispatch receiver (da classe):
class Logger {
fun String.logInfo() {
// 'this' se refere ao String (extension receiver)
// 'this@Logger' se refere ao Logger (dispatch receiver)
println("[INFO] $this")
}
}
fun main() {
val logger = Logger()
with(logger) {
"Mensagem de teste".logInfo() // [INFO] Mensagem de teste
}
}
Quando usar receivers
- Extension functions: para adicionar funcionalidade a tipos existentes sem heranca.
- DSLs: para criar APIs que leem como linguagem natural.
- Builders: para construir objetos complexos com sintaxe fluente.
- Scope functions: para operacoes rapidas em objetos sem criar variaveis intermediarias.
- Configuracao: para blocos de configuracao onde o contexto e claro e os metodos disponiveis sao limitados.
Erros comuns
Receiver errado em escopos aninhados: quando voce tem lambdas aninhadas com diferentes receivers,
thisse refere ao mais interno. Usethis@NomeDaFuncaopara acessar receivers externos.Abusar de extension functions: adicionar muitas extensions a tipos comuns como
StringouIntpolui o namespace e dificulta a descoberta de funcionalidade.Nao usar @DslMarker: sem essa anotacao, DSLs permitem chamadas de metodos de escopos externos, levando a erros sutis.
Confundir
T.() -> Unitcom(T) -> Unit: a primeira e uma lambda with receiver (acesso viathis); a segunda e uma lambda com parametro (acesso via nome do parametro ouit).Esquecer que extension functions nao tem acesso a membros privados: o receiver
thisda acesso apenas a membros publicos e internos do tipo, nao a membros privados ou protegidos.
Termos relacionados
- Extension Function: funcao que adiciona metodos a um tipo usando receiver.
- Context Receiver: recurso experimental que permite multiplos receivers implicitos.
- Scope Functions:
let,run,with,apply,also– funcoes que manipulam receivers. - DSL: Domain Specific Language, construida usando lambdas with receiver.
- this: palavra-chave que referencia o receiver atual.
- Lambda: expressao funcional que pode ter receiver (
T.() -> Unit).
Receivers sao um dos conceitos mais poderosos e distintos do Kotlin. Eles permitem que o codigo expresse intencao de forma clara e concisa, transformando operacoes complexas em blocos legiveis que parecem parte da linguagem. Dominar receivers e dominar a essencia do Kotlin idiomatico.