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

  1. Receiver errado em escopos aninhados: quando voce tem lambdas aninhadas com diferentes receivers, this se refere ao mais interno. Use this@NomeDaFuncao para acessar receivers externos.

  2. Abusar de extension functions: adicionar muitas extensions a tipos comuns como String ou Int polui o namespace e dificulta a descoberta de funcionalidade.

  3. Nao usar @DslMarker: sem essa anotacao, DSLs permitem chamadas de metodos de escopos externos, levando a erros sutis.

  4. Confundir T.() -> Unit com (T) -> Unit: a primeira e uma lambda with receiver (acesso via this); a segunda e uma lambda com parametro (acesso via nome do parametro ou it).

  5. Esquecer que extension functions nao tem acesso a membros privados: o receiver this da 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.