O que e Context Receiver em Kotlin?
Context Receivers sao um recurso do Kotlin que permite declarar que uma funcao ou propriedade requer um ou mais contextos implicitos para ser chamada. Diferente de extension functions, que tem um unico receiver, context receivers permitem que uma funcao dependa de multiplos objetos de contexto sem precisar recebe-los como parametros explicitos.
Esse recurso foi introduzido como experimental a partir do Kotlin 1.6.20 e continua evoluindo. Ele resolve um problema antigo: como expressar que uma funcao precisa de multiplos “ambientes” para funcionar, sem poluir a assinatura com muitos parametros.
O problema que Context Receivers resolvem
Imagine que voce tem uma funcao que precisa de acesso a um logger e a uma transacao de banco de dados. Sem context receivers, voce teria estas opcoes:
// Opcao 1: Parametros explicitos (verboso)
fun salvarUsuario(usuario: Usuario, logger: Logger, transacao: Transacao) {
logger.info("Salvando usuario...")
transacao.executar { /* salvar */ }
}
// Opcao 2: Extension function (limitado a um receiver)
fun Logger.salvarUsuario(usuario: Usuario, transacao: Transacao) {
info("Salvando usuario...")
transacao.executar { /* salvar */ }
}
Nenhuma dessas opcoes expressa claramente que a funcao depende de ambos os contextos. Com context receivers, a intencao fica explicita:
context(Logger, Transacao)
fun salvarUsuario(usuario: Usuario) {
info("Salvando usuario...")
executar { /* salvar */ }
}
Sintaxe e declaracao
A sintaxe usa a palavra-chave context seguida dos tipos de contexto entre parenteses:
context(LoggerContext)
fun registrarEvento(mensagem: String) {
log("Evento: $mensagem")
}
context(LoggerContext, MetricasContext)
fun registrarComMetricas(mensagem: String) {
log("Evento: $mensagem")
incrementarContador("eventos.registrados")
}
Para chamar essas funcoes, voce precisa estar em um escopo onde os contextos estejam disponiveis:
interface LoggerContext {
fun log(mensagem: String)
}
interface MetricasContext {
fun incrementarContador(nome: String)
}
fun main() {
val logger = object : LoggerContext {
override fun log(mensagem: String) = println(mensagem)
}
val metricas = object : MetricasContext {
override fun incrementarContador(nome: String) = println("$nome +1")
}
with(logger) {
with(metricas) {
registrarComMetricas("Usuario criado")
}
}
}
Exemplo pratico: DSL com multiplos contextos
Context receivers brilham na criacao de DSLs onde diferentes escopos precisam estar disponiveis:
interface HtmlContext {
fun tag(nome: String, conteudo: String)
}
interface CssContext {
fun estilo(seletor: String, propriedade: String, valor: String)
}
context(HtmlContext, CssContext)
fun componenteEstilizado(nome: String, texto: String) {
estilo(".$nome", "color", "blue")
estilo(".$nome", "font-weight", "bold")
tag("div class='$nome'", texto)
}
Isso permite que a funcao componenteEstilizado acesse tanto a API de HTML quanto a de CSS sem receber nenhum parametro adicional.
Context Receivers em classes
Voce tambem pode usar context receivers em classes e propriedades:
interface Configuracao {
val maxTentativas: Int
val timeout: Long
}
context(Configuracao)
class ServicoDeRetentativa {
fun <T> executar(bloco: () -> T): T {
var tentativa = 0
while (true) {
try {
return bloco()
} catch (e: Exception) {
tentativa++
if (tentativa >= maxTentativas) throw e
Thread.sleep(timeout)
}
}
}
}
context(Configuracao)
val tempoTotalMaximo: Long
get() = maxTentativas * timeout
Context Receivers vs Extension Functions
A diferenca fundamental e a quantidade de receivers:
// Extension function: um unico receiver
fun String.formatarComoTitulo(): String {
return this.split(" ").joinToString(" ") { it.replaceFirstChar { c -> c.uppercase() } }
}
// Context receiver + extension: multiplos contextos
context(Locale)
fun String.formatarLocalizado(): String {
return this.uppercase(this@Locale)
}
Extension functions sao ideais quando a funcao e logicamente um “metodo” daquele tipo. Context receivers sao para quando a funcao precisa de um ambiente/contexto para funcionar, mas nao e um metodo do contexto.
Habilitando context receivers
Como o recurso ainda e experimental, voce precisa habilita-lo no build.gradle.kts:
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs += "-Xcontext-receivers"
}
}
Sem essa flag, o compilador nao reconhecera a sintaxe context(...).
Quando usar Context Receivers
- DSLs complexas: quando o DSL tem multiplas camadas de contexto que precisam estar acessiveis simultaneamente.
- Injecao de dependencias implicita: quando funcoes dependem de servicos ou configuracoes que deveriam estar “no ambiente” sem serem passados explicitamente.
- Separacao de concerns: quando voce quer expressar que uma funcao depende de capacidades especificas (logging, metricas, transacao) sem acoplar a implementacoes concretas.
- Reducao de parametros: quando muitas funcoes no mesmo modulo compartilham os mesmos parametros de contexto.
Evite usar quando a funcao tem apenas um contexto (use extension function) ou quando os parametros sao dados, nao ambientes (use parametros normais).
Erros comuns
Esquecer de habilitar a flag do compilador: sem
-Xcontext-receivers, o codigo nao compila e a mensagem de erro pode ser confusa.Abusar de context receivers: usar contextos para tudo transforma o codigo em algo dificil de rastrear. Se o leitor nao consegue entender de onde vem um metodo, o codigo ficou implicito demais.
Ambiguidade entre receivers: se dois contextos tem metodos com o mesmo nome, o compilador pode ter dificuldade em resolver qual usar. Use qualificadores explicitos nesses casos.
Ignorar que e experimental: o recurso pode mudar em versoes futuras do Kotlin. Use com cautela em codigo de producao e esteja preparado para adaptar.
Confundir com scope functions:
with,applyeruncriam escopos de receiver, mas nao sao a mesma coisa que context receivers. Context receivers sao verificados em tempo de compilacao na assinatura da funcao.
Termos relacionados
- Receiver: o objeto sobre o qual uma extension function opera, acessivel via
this. - Extension Function: funcao que adiciona comportamento a um tipo existente, usando um unico receiver.
- DSL: Domain Specific Language, onde context receivers permitem criar APIs fluentes com multiplos contextos.
- Scope Functions:
let,run,with,apply,also– funcoes que criam escopos temporarios de receiver. - Inline: funcoes inline combinadas com context receivers podem eliminar overhead de criacao de objetos.
Context receivers representam uma evolucao significativa na expressividade do Kotlin, permitindo que funcoes declarem suas dependencias de contexto de forma clara e verificada pelo compilador. Embora ainda experimental, o recurso ja mostra grande potencial para simplificar APIs e DSLs complexas.