Rust vs Kotlin: Server-Side e Mobile 2026 | Rust Brasil

Kotlin vs Rust: JVM vs nativo, Android NDK, Ktor vs Axum e coroutines vs async. Qual escolher para backend e mobile?

Introdução

Kotlin e Rust são duas linguagens modernas que surgiram para resolver problemas de linguagens mais antigas — Kotlin como uma alternativa melhor ao Java, e Rust como uma alternativa segura ao C/C++. Apesar de terem origens diferentes, seus caminhos se cruzam cada vez mais em desenvolvimento backend e aplicações mobile de alta performance.

Kotlin, criada pela JetBrains e adotada pelo Google como linguagem oficial para Android, roda na JVM e oferece uma experiência de desenvolvimento moderna com null safety, coroutines e interoperabilidade total com Java. Rust compila para código nativo, oferecendo performance superior e controle absoluto sobre recursos.

Este artigo é para desenvolvedores Kotlin que estão explorando Rust, desenvolvedores Android que precisam de performance nativa (NDK) e arquitetos que avaliam stacks para novos serviços backend.

Tabela Comparativa

AspectoRustKotlin
PlataformaCódigo nativo (LLVM)JVM (+ Kotlin/Native, Kotlin/JS)
Null safetyOption<T> (sem null)Nullable types (String?) em compilação
Concorrênciaasync/await + threads + channelsCoroutines (suspending functions)
Gerenciamento de memóriaOwnership + Borrow CheckerGarbage Collector (JVM)
PerformancePróxima de CPróxima de Java (~2-5x mais lenta que Rust)
Curva de aprendizadoÍngreme (ownership, lifetimes)Moderada (familiar para devs Java/Swift)
IDEVS Code, RustRover, NeovimIntelliJ IDEA, Android Studio
Frameworks webAxum, Actix WebKtor, Spring Boot (Kotlin)
MobileVia NDK (C ABI)Android nativo, Kotlin Multiplatform
Ecossistemacrates.io (~150k)Maven Central (+ ecossistema Java inteiro)

Null Safety: Abordagens Diferentes

Ambas as linguagens tratam null safety como prioridade, mas de formas diferentes.

Kotlin: Nullable Types

data class Usuario(
    val nome: String,      // nunca null
    val email: String?,    // pode ser null
    val idade: Int
)

fun saudacao(usuario: Usuario): String {
    // Safe call operator (?.) e Elvis operator (?:)
    val dominio = usuario.email?.substringAfter("@") ?: "sem email"
    return "Olá, ${usuario.nome}! Domínio: $dominio"
}

fun main() {
    val user1 = Usuario("Ana", "ana@rust.com.br", 28)
    val user2 = Usuario("Bob", null, 35)

    println(saudacao(user1)) // Olá, Ana! Domínio: rust.com.br
    println(saudacao(user2)) // Olá, Bob! Domínio: sem email
}

Rust: Option<T>

struct Usuario {
    nome: String,
    email: Option<String>, // explicitamente opcional
    idade: u32,
}

fn saudacao(usuario: &Usuario) -> String {
    let dominio = usuario
        .email
        .as_ref()
        .and_then(|e| e.split('@').nth(1))
        .unwrap_or("sem email");

    format!("Olá, {}! Domínio: {dominio}", usuario.nome)
}

fn main() {
    let user1 = Usuario {
        nome: "Ana".into(),
        email: Some("ana@rust.com.br".into()),
        idade: 28,
    };
    let user2 = Usuario {
        nome: "Bob".into(),
        email: None,
        idade: 35,
    };

    println!("{}", saudacao(&user1)); // Olá, Ana! Domínio: rust.com.br
    println!("{}", saudacao(&user2)); // Olá, Bob! Domínio: sem email
}

Kotlin é mais conciso com operadores como ?. e ?:. Rust é mais explícito, usando combinadores como and_then, map e unwrap_or. Ambas as abordagens eliminam NullPointerException em tempo de compilação.

Concorrência: Coroutines vs Async/Await

Kotlin Coroutines

import kotlinx.coroutines.*

data class Produto(val nome: String, val preco: Double)

suspend fun buscarPreco(nome: String): Double {
    delay(100) // simula chamada de rede
    return when (nome) {
        "Notebook" -> 3500.0
        "Mouse" -> 150.0
        "Teclado" -> 280.0
        else -> 0.0
    }
}

suspend fun buscarProdutos(nomes: List<String>): List<Produto> =
    coroutineScope {
        nomes.map { nome ->
            async {
                val preco = buscarPreco(nome)
                Produto(nome, preco)
            }
        }.awaitAll()
    }

fun main() = runBlocking {
    val produtos = buscarProdutos(listOf("Notebook", "Mouse", "Teclado"))
    produtos.forEach { println("${it.nome}: R$${it.preco}") }
    val total = produtos.sumOf { it.preco }
    println("Total: R$$total")
}

Rust com Tokio

use tokio::time::{sleep, Duration};

struct Produto {
    nome: String,
    preco: f64,
}

async fn buscar_preco(nome: &str) -> f64 {
    sleep(Duration::from_millis(100)).await; // simula chamada de rede
    match nome {
        "Notebook" => 3500.0,
        "Mouse" => 150.0,
        "Teclado" => 280.0,
        _ => 0.0,
    }
}

async fn buscar_produtos(nomes: &[&str]) -> Vec<Produto> {
    let futures: Vec<_> = nomes
        .iter()
        .map(|&nome| async move {
            let preco = buscar_preco(nome).await;
            Produto {
                nome: nome.to_string(),
                preco,
            }
        })
        .collect();

    futures::future::join_all(futures).await
}

#[tokio::main]
async fn main() {
    let nomes = vec!["Notebook", "Mouse", "Teclado"];
    let produtos = buscar_produtos(&nomes).await;

    for p in &produtos {
        println!("{}: R${:.2}", p.nome, p.preco);
    }
    let total: f64 = produtos.iter().map(|p| p.preco).sum();
    println!("Total: R${total:.2}");
}

Kotlin coroutines são mais ergonômicas — async/await é mais natural e o coroutineScope gerencia o ciclo de vida automaticamente. Em Rust, você precisa entender futures, pinning e escolher o runtime (Tokio). Em troca, Rust não tem overhead de runtime e usa threads reais do sistema operacional.

Exemplo Prático: API REST

Kotlin com Ktor

import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.routing.*
import io.ktor.server.response.*
import io.ktor.server.request.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.plugins.contentnegotiation.*
import kotlinx.serialization.Serializable
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicLong

@Serializable
data class Tarefa(val id: Long, val titulo: String, val concluida: Boolean = false)

@Serializable
data class CriarTarefa(val titulo: String)

fun main() {
    val tarefas = ConcurrentHashMap<Long, Tarefa>()
    val contador = AtomicLong()

    embeddedServer(Netty, port = 3000) {
        install(ContentNegotiation) { json() }

        routing {
            get("/tarefas") {
                call.respond(tarefas.values.toList())
            }
            post("/tarefas") {
                val dto = call.receive<CriarTarefa>()
                val id = contador.incrementAndGet()
                val tarefa = Tarefa(id, dto.titulo)
                tarefas[id] = tarefa
                call.respond(tarefa)
            }
        }
    }.start(wait = true)
}

Rust com Axum

use axum::{extract::State, routing::{get, post}, Json, Router};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{atomic::{AtomicU64, Ordering}, Arc, RwLock};

#[derive(Clone, Serialize)]
struct Tarefa {
    id: u64,
    titulo: String,
    concluida: bool,
}

#[derive(Deserialize)]
struct CriarTarefa {
    titulo: String,
}

struct AppState {
    tarefas: RwLock<HashMap<u64, Tarefa>>,
    contador: AtomicU64,
}

async fn listar(State(state): State<Arc<AppState>>) -> Json<Vec<Tarefa>> {
    let tarefas = state.tarefas.read().unwrap();
    Json(tarefas.values().cloned().collect())
}

async fn criar(
    State(state): State<Arc<AppState>>,
    Json(dto): Json<CriarTarefa>,
) -> Json<Tarefa> {
    let id = state.contador.fetch_add(1, Ordering::Relaxed) + 1;
    let tarefa = Tarefa { id, titulo: dto.titulo, concluida: false };
    state.tarefas.write().unwrap().insert(id, tarefa.clone());
    Json(tarefa)
}

#[tokio::main]
async fn main() {
    let state = Arc::new(AppState {
        tarefas: RwLock::new(HashMap::new()),
        contador: AtomicU64::new(0),
    });

    let app = Router::new()
        .route("/tarefas", get(listar).post(criar))
        .with_state(state);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();
    axum::serve(listener, app).await.unwrap();
}

Ktor é elegante e produtivo, especialmente para quem vem do ecossistema Kotlin/JVM. Axum é mais verbose, mas o binário resultante consome muito menos memória e processa mais requisições por segundo. Para um guia completo de APIs em Rust, confira nosso tutorial de API REST com Axum.

Comparação de Performance

Benchmarks de Servidor Web

MétricaRust (Axum)Kotlin (Ktor/Netty)Kotlin (Spring Boot)
Requisições/s (JSON)~850.000~180.000~100.000
Latência p990,8 ms4 ms9 ms
Startup< 5 ms~1.500 ms~4.000 ms
Memória idle~5 MB~120 MB~250 MB
Memória sob carga~25 MB~300 MB~600 MB

Android: NDK com Rust

Para aplicações Android que precisam de performance nativa (processamento de imagem, criptografia, áudio), Rust via NDK é uma alternativa excelente ao C/C++:

// Biblioteca Rust para Android via JNI
#[no_mangle]
pub extern "C" fn Java_com_exemplo_RustLib_processar(
    _env: jni::JNIEnv,
    _class: jni::objects::JClass,
    dados: jni::sys::jlong,
) -> jni::sys::jlong {
    let valor = dados as i64;
    // Processamento intensivo aqui
    (valor * 2 + 42) as jni::sys::jlong
}
// Chamando Rust a partir de Kotlin no Android
class RustLib {
    companion object {
        init {
            System.loadLibrary("minha_lib_rust")
        }
    }
    external fun processar(dados: Long): Long
}

fun main() {
    val resultado = RustLib().processar(100)
    println("Resultado do Rust: $resultado") // 242
}

Quando Usar Kotlin

Escolha Kotlin quando:

  • Desenvolvimento Android: linguagem oficial, excelente tooling com Android Studio
  • Kotlin Multiplatform: compartilhar código entre Android, iOS, web e desktop
  • Backend JVM: quando você precisa do ecossistema Java (Spring, Kafka, Hibernate)
  • Equipe vinda de Java: Kotlin é uma transição natural e suave
  • Prototipagem rápida: coroutines, null safety e DSLs tornam o desenvolvimento ágil

Quando Usar Rust

Escolha Rust quando:

  • Performance nativa é essencial: processamento de dados, algoritmos intensivos
  • Consumo de memória importa: serverless, edge computing, IoT
  • Componentes Android de alta performance: via NDK para processamento pesado
  • Infraestrutura e ferramentas: CLIs, servidores de alta carga, proxies
  • WebAssembly: lógica compartilhada entre plataformas via Wasm

Conclusão e Recomendação

Para desenvolvimento Android e backend JVM convencional, Kotlin é a escolha mais produtiva. O ecossistema é maduro, a linguagem é expressiva e a integração com ferramentas Java é perfeita. Kotlin Multiplatform está se tornando uma opção viável para compartilhar código entre plataformas.

Para serviços de alta performance, CLIs e componentes nativos, Rust é superior. Se você tem um microsserviço que precisa de baixa latência e pouca memória, ou um módulo Android que faz processamento pesado, Rust é a escolha certa.

A combinação ideal para muitas equipes é usar Kotlin para a camada de aplicação (Android, backend com Ktor/Spring) e Rust para componentes críticos de performance acessados via FFI/JNI. Essa abordagem oferece produtividade onde importa e performance onde é necessário.


Veja Também