---
title: "Rust para Aplicativos Móveis: Android e iOS com JNI, UniFFI e Tauri Mobile em 2026"
url: "https://rustlang.com.br/blog/rust-mobile-android-ios-uniffi-jni-tauri-2026/"
markdown_url: "https://rustlang.com.br/blog/rust-mobile-android-ios-uniffi-jni-tauri-2026.MD"
description: "Como levar Rust para apps Android e iOS em 2026: JNI, bindings nativos, UniFFI, cargo-mobile, Tauri 2 Mobile, integração com Kotlin/Swift, toolchains cross-compile, CI e quando vale a pena frente a Dart, C++ e Kotlin Multiplatform."
date: "2026-06-22"
author: "Equipe Rust Brasil"
---

# Rust para Aplicativos Móveis: Android e iOS com JNI, UniFFI e Tauri Mobile em 2026

Como levar Rust para apps Android e iOS em 2026: JNI, bindings nativos, UniFFI, cargo-mobile, Tauri 2 Mobile, integração com Kotlin/Swift, toolchains cross-compile, CI e quando vale a pena frente a Dart, C++ e Kotlin Multiplatform.


## Por que Rust aparece cada vez mais dentro de apps Android e iOS

Durante anos a regra era simples: Android em Kotlin/Java, iOS em Swift/Objective-C, e o máximo de compartilhamento entre as duas plataformas vinha de C++ nativo ou de frameworks como Flutter e React Native. O problema é que C++ é custoso de manter e expõe falhas de memória e concorrência, enquanto as ferramentas cross-platform baseadas em linguagens gerenciadas não entregam o desempenho necessário para criptografia, parsing, sync offline, compressão e inferência de modelos em dispositivos fracos.

Rust mudou esse cenário porque oferece três coisas ao mesmo tempo: **desempenho equivalente ao C++**, **garantias de memória e concorrência sem garbage collector**, e **bindings interoperáveis** com Kotlin, Swift, JavaScript e Python. O resultado é um padrão arquitetural que empresas como Mozilla, Cloudflare, Signal, 1Password e Discord já usam em produção: um núcleo Rust compartilhado entre backend, web e mobile, com a UI permanecendo nativa em cada plataforma.

Para quem busca [vagas Rust](/vagas/) e quer atuar no mercado mobile, esse é um nicho em ascensão. Times brasileiros de fintech, saúde digital e mensageria já procuram engenheiras e engenheiros que saibam expor uma biblioteca Rust como SDK consumível por Kotlin e Swift. A [carreira Rust](/carreira/) em mobile ainda é pouco disputada comparada ao backend puro, o que faz do domínio de UniFFI, JNI e Tauri Mobile um diferencial concreto — visível inclusive no nosso comparativo [Rust vs C++ em 2026](/blog/rust-vs-cpp-2026/).

## Os quatro caminhos para levar Rust ao mobile em 2026

Existem quatro estratégias práticas, cada uma com custo e ganho diferentes. Escolher a certa antes de começar economiza semanas de retrabalho.

**1. Núcleo compartilhado com UniFFI** — A Mozilla mantém o UniFFI para gerar bindings idiomáticos a partir de uma interface declarativa. Você escreve a lógica em Rust, descreve a API em um arquivo `.udl` (ou usando macros proc `#[uniffi::export]`), e o UniFFI gera código Kotlin para Android e Swift para iOS pronto para uso. É o caminho mais limpo para novo código e o que melhor envelhece, porque isola o desenvolvedor mobile dos detalhes de FFI.

**2. JNI direto no Android** — Quando você precisa de controle fino sobre a JVM, ou quando integra código legado, chamar Rust via JNI continua sendo a rota. O crate `jni` encapsula a ligação com `JNIEnv`, permite registrar callbacks e manipular objetos Kotlin a partir do Rust. É mais verboso e exige cuidado com threads (a `JNIEnv` é por-thread), mas é o padrão para interagir com APIs da plataforma Android.

**3. Tauri 2 Mobile** — O Tauri, já coberto no nosso [guia de GUI em Rust 2026](/blog/rust-gui-2026/), ganhou no Tauri 2 suporte oficial a Android e iOS. Você escreve a UI em HTML/JS (ou em frameworks como Svelte, Vue, React) e o backend em Rust, usando o WebView nativo de cada plataforma. É uma boa escolha quando a equipe já domina web e quer um app com footprint pequeno, binários leves e lógica de domínio em Rust.

**4. Cross-compile manual para bibliotecas nativas** — Em projetos que só precisam embutir uma `.so` ou `.framework` sem camada de alto nível, compilar diretamente com as toolchains NDK e iOS SDK resolve. É o que fazem muitos SDKs de criptografia e parsing, expostos depois via FFI bruto ou via UniFFI.

## Preparando a toolchain: NDK e iOS SDK

Antes de escrever código, o ambiente precisa saber compilar para os alvos mobile. O ponto de partida é instalar os alvos Rust corretos e configurar linkers dedicados.

```bash
# Alvos Android (escolha conforme a ABI alvo)
rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android

# Alvos iOS
rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios
```

Para Android, instale o Android NDK (via Android Studio ou `sdkmanager`) e configure o linker de cada alvo em `~/.cargo/config.toml`. O caminho do NDK muda a cada versão; em 2026 a prática recomendada é usar a toolchain `llvm` dentro de `ndk/toolchains/llvm/prebuilt/<host>/bin`.

```toml
# .cargo/config.toml do projeto
[target.aarch64-linux-android]
linker = "/Android/Sdk/ndk/27.0.12077973/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang"

[target.armv7-linux-androideabi]
linker = "/Android/Sdk/ndk/27.0.12077973/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi24-clang"
```

Para iOS, a integração com Xcode é mais simples porque o `cargo-lipo` foi incorporado ao Rust. Basta compilar com `cargo build --target aarch64-apple-ios` e depois agrupar as arquiteturas em um fat binary `.a`, que entra como framework estático no projeto Xcode.

A ferramenta `cargo-ndk` automatiza essa configuração no Android, aceitando o nível de API (`--android-platform 24`) e repassando para o cargo. Em CI, é o que mantém os builds reproduzíveis entre GitHub Actions, GitLab CI e runners locais.

## UniFFI: o caminho idiomático

O UniFFI é hoje a forma mais produtiva de expor Rust para mobile. O fluxo começa com uma crate de biblioteca cuja API pública é descrita em uma interface. Veja um núcleo mínimo que criptografa e descriptografa uma mensagem — exemplo realista de uma carteira digital.

```toml
# Cargo.toml
[lib]
crate-type = ["cdylib", "staticlib", "lib"]

[dependencies]
uniffi = { version = "0.28", features = ["cli"] }

[build-dependencies]
uniffi = { version = "0.28", features = ["build"] }
```

```rust
// src/lib.rs
uniffi::include_scaffolding!("wallet");

pub struct MensagemCriptografada {
    pub nonce: Vec<u8>,
    pub cifrado: Vec<u8>,
}

pub fn cifrar(chave: Vec<u8>, texto: String) -> Result<MensagemCriptografada, ErroCarteira> {
    if chave.len() != 32 {
        return Err(ErroCarteira::ChaveInvalida);
    }
    // Lógica real usando, por exemplo, a crate `crypto-box` ou `aes-gcm`
    Ok(MensagemCriptografada { nonce: vec![0u8; 12], cifrado: texto.into_bytes() })
}

pub fn decifrar(chave: Vec<u8>, msg: MensagemCriptografada) -> Result<String, ErroCarteira> {
    if chave.len() != 32 {
        return Err(ErroCarteira::ChaveInvalida);
    }
    Ok(String::from_utf8(msg.cifrado).map_err(|_| ErroCarteira::ConteudoInvalido)?)
}

#[derive(Debug, thiserror::Error, uniffi::Error)]
pub enum ErroCarteira {
    #[error("chave inválida")]
    ChaveInvalida,
    #[error("conteúdo inválido")]
    ConteudoInvalido,
}
```

A interface em `src/wallet.udl` descreve os tipos expostos:

```
namespace wallet {
  [Throws=ErroCarteira]
  MensagemCriptografada cifrar([ByRef] sequence<u8> chave, string texto);

  [Throws=ErroCarteira]
  string decifrar([ByRef] sequence<u8> chave, MensagemCriptografada msg);
};

dictionary MensagemCriptografada {
  sequence<u8> nonce;
  sequence<u8> cifrado;
};

[Error]
enum ErroCarteira {
  "ChaveInvalida",
  "ConteudoInvalido",
};
```

Ao rodar `cargo build --target aarch64-linux-android`, o UniFFI gera o arquivo `wallet.kt` com classes idiomáticas em Kotlin. No iOS, `cargo build --target aarch64-apple-ios` produz `wallet.swift`. O desenvolvedor mobile simplesmente importa o arquivo no projeto e consome a API como se fosse nativa:

```kotlin
// Android (Kotlin)
import uniffi.wallet.cifrar
import uniffi.wallet.ErroCarteira

try {
    val msg = cifrar(chave = chave32bytes, texto = "segredo")
    saveToDisk(msg)
} catch (e: ErroCarteira) {
    Log.e("Wallet", "falha ao cifrar", e)
}
```

```swift
// iOS (Swift)
import wallet

do {
    let msg = try cifrar(chave: chave32bytes, texto: "segredo")
    try saveToDisk(msg)
} catch let e as ErroCarteira {
    print("falha ao cifrar: \(e)")
}
```

Esse desenho entrega três vantagens concretas. Primeiro, o tratamento de erro é tipado em ambas as plataformas — `ErroCarteira` vira uma `sealed class` em Kotlin e um `enum` em Swift. Segundo, os tipos de dados (records/dictionaries) cruzam a fronteira sem código manual de marshalling. Terceiro, o mesmo binário Rust pode ser testado em [testes unitários](/tutoriais/testes-rust/) antes de sequer tocar o lado mobile, reduzindo o ciclo de feedback.

## JNI direto: controle fino da VM Android

Quando o UniFFI não cobre o caso — tipicamente em integrações com APIs específicas do Android, como `Context`, `ContentResolver` ou ciclo de vida de `Activity` — a rota continua sendo JNI via crate `jni`. O padrão é registrar uma função `#[no_mangle]` que recebe um `JNIEnv` e um `JClass`:

```rust
use jni::objects::{JClass, JString, JValue};
use jni::JNIEnv;

#[no_mangle]
pub extern "system" fn Java_br_rustbr_wallet_Wallet_cifrar<'local>(
    mut env: JNIEnv<'local>,
    _class: JClass<'local>,
    chave: JString<'local>,
    texto: JString<'local>,
) -> JString<'local> {
    let chave: String = env.get_string(&chave).unwrap().into();
    let texto: String = env.get_string(&texto).unwrap().into();

    let resultado = format!("cifrado:{}:{}", chave.len(), texto.len());

    env.new_string(resultado).unwrap()
}
```

O lado Kotlin declara a função como `external` dentro de um `companion object` e carrega a biblioteca nativa:

```kotlin
object Wallet {
    init { System.loadLibrary("wallet") }
    external fun cifrar(chave: String, texto: String): String
}
```

A atenção necessária com JNI é real: `JNIEnv` só é válido na thread que o recebeu, então qualquer chamada a partir de uma task Tokio precisa ser precedida por `env.attach_current_thread()`; strings UTF-8 precisam ser convertidas com cuidado; e exceções lançadas a partir de Kotlin tornam o estado da JVM inconsistente até a próxima chamada detectar `env.exception_check()`. Para código novo, UniFFI cobre 90% dos casos sem esse custo; JNI direto fica reservado para integrações profundas com o framework Android.

## Tauri 2 Mobile: web UI sobre núcleo Rust

O Tauri 2 trouxe oficialmente o mobile como cidadão de primeira classe. Em vez de gerar bindings manuais, você declara comandos Rust consumíveis pelo frontend JavaScript, e o Tauri cuida do transporte entre WebView e backend nativo. Veja um projeto mínimo:

```bash
cargo install create-tauri-app
cargo create-tauri-app wallet-app
# escolha: TypeScript + Svelte, gerenciador pnpm
cd wallet-app
cargo tauri android init
cargo tauri ios init
cargo tauri android dev
```

No `src-tauri/src/lib.rs`, o comando exposto fica assim:

```rust
#[tauri::command]
fn cifrar(chave: String, texto: String) -> Result<String, String> {
    if chave.len() != 32 {
        return Err("chave deve ter 32 bytes".into());
    }
    Ok(format!("cifrado:{}:{}", chave.len(), texto))
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![cifrar])
        .run(tauri::generate_context!())
        .expect("erro ao iniciar o app");
}
```

O frontend chama o comando pela API do Tauri:

```ts
import { invoke } from "@tauri-apps/api/core";

const resultado = await invoke<string>("cifrar", {
  chave: chave32bytes,
  texto: "segredo",
});
```

O ganho do Tauri Mobile é concentration de stack: o mesmo código Rust, a mesma UI web, deployando para Android, iOS, Linux, macOS e Windows. O custo é depender do WebView nativo (o que varia entre dispositivos Android e exige cuidado com versões mínimas), e o tamanho de binário maior do que uma biblioteca pura. Em 2026 ele já é uma escolha madura para apps corporativos e de produtividade, mas ainda não substitui o desenho UniFFI + UI nativa para apps com exigências visuais profundas.

## CI: cross-compile reprodutível em pipelines

O calcanhar de Aquiles do Rust mobile é o CI lento. Cada arquitetura exige um build independente e, em apps reais, são quatro alvos Android mais dois iOS. As práticas que mantêm o pipeline saudável são três.

Primeiro, use cache de cargo com escopo por alvo. Ferramentas como `Swatinem/rust-cache` distinguem targets por `sharedKey`, evitando invalidar o cache inteiro quando só um alvo muda. Segundo, paralelize os alvos em jobs distintos, cada um publicando o `.so`/`.a` como artefato. Um job final de montagem junta tudo no `.aar` (Android) ou `.xcframework` (iOS) consumido pelos builds nativos. Terceiro, prefira `cargo-ndk` e `xcodebuild` com versões fixas, e versione o NDK no repositório — atualizar o NDK sem testar quebra builds silenciosamente.

Um padrão comum em SDKs brasileiros é manter um repositório separado só para a biblioteca Rust, que publica artefatos no GitHub Releases ou em um repositório Maven privado. O app Android consome via Gradle como qualquer `.aar`, e o app iOS via Swift Package Manager apontando para o `.xcframework`. Isso separa o ciclo de release da UI do ciclo de release do núcleo — fundamental quando o núcleo é compartilhado entre mobile, web e backend.

## Desempenho e tamanho de binário

Levar Rust ao mobile só vale quando o ganho de desempenho ou a garantia de segurança compensam o custo de manter uma toolchain extra. Os pontos onde Rust brilha em apps reais são consistentes: criptografia ponta a ponta (reduz latência de handshake em 40-70% vs equivalente em linguagens gerenciadas), parsing de JSON/protobuf grandes em streaming, compressão de imagens e áudio, inferência de modelos pequenos com `ort` (ONNX Runtime), e sync offline baseado em CRDTs — tema que aprofundamos em [Rust local-first e CRDT](/blog/rust-local-first-crdt-colaboracao-2026/).

Para manter o tamanho do binário baixo, três práticas: ative `lto = "thin"` e `codegen-units = 1` no `Cargo.toml` do release, use `panic = "abort"` quando o app puder travar e reiniciar (comum em mobile), e audite dependências grandes com `cargo bloat`. Uma biblioteca UniFFI bem ajustada entrega `.so` de 1-3 MB por arquitetura; um app Tauri Mobile completo costuma ficar entre 8 e 15 MB, ainda bem abaixo de equivalentes Electron.

## Quando Rust no mobile faz sentido (e quando não faz)

A pergunta honesta antes de começar é: este app realmente precisa de Rust? A resposta costuma ser **sim** quando pelo menos um destes pontos aparece:

- há um núcleo de lógica reutilizável entre Android, iOS e web (ou backend);
- o app manipula criptografia, parsing de grandes volumes, compressão ou ML;
- a equipe precisa de garantias de segurança de memória que C++ não entrega;
- existe um SDK corporativo exposto para parceiros em múltiplas plataformas;
- o app processa mídia, áudio ou vídeo em tempo real.

A resposta é **não** quando o app é predominantemente UI consumindo APIs REST, sem lógica pesada. Nesse caso, Kotlin, Swift, Flutter ou React Native entregam o produto mais rápido, com custo de manutenção menor e sem o overhead de toolchain cross-compile. Escolher Rust por modismo, sem um problema de desempenho ou compartilhamento concreto, é um erro comum que transforma um app simples em um projeto caro.

O ponto de equilíbrio que tem funcionado em produção é o modelo **núcleo compartilhado**: toda a lógica sensível a desempenho e segurança vive em Rust, exposta via UniFFI, e cada plataforma mantém sua UI nativa. Isso preserva a experiência do usuário (UI nativa, rápida, acessível), ao mesmo tempo em que centraliza o código crítico em um lugar só — testável com [ferramentas como proptest](/blog/rust-proptest-fuzzing-property-based-testing-2026/) e observável com [tracing e OpenTelemetry](/blog/rust-opentelemetry-producao-2026/).

## Conclusão

Rust em mobile deixou de ser experimento e virou arquitetura de produção em apps como Firefox, Signal e 1Password. Em 2026, o caminho recomendado para novos projetos é UniFFI para bindings idiomáticos, JNI direto apenas onde a integração com a VM Android exigir, e Tauri 2 Mobile para equipes que preferem UI web e querem reuso entre desktop e mobile. A toolchain cross-compile amadureceu (`cargo-ndk`, `cargo-lipo`, iOS SDK direto), e o CI paralelo por alvo mantém builds reproduzíveis.

Para quem está construindo carreira, dominar a fronteira entre Rust, Kotlin e Swift é um nicho pouco disputado e bem remunerado — especialmente em fintechs, saúde e mensageria, onde segurança de memória e desempenho determinam o produto. Comece expondo uma biblioteca pequena via UniFFI, meça tamanho de binário e latência em um dispositivo real, e cresça a partir da evidência. O ecossistema brasileiro de empresas que usam Rust valoriza exatamente engenheiras e engenheiros que sabem levar o núcleo crítico do servidor até o bolso do usuário sem abrir mão de segurança ou velocidade.

---

**Leia também:**
- [Frameworks GUI em Rust 2026](/blog/rust-gui-2026/)
- [Rust Local-First e CRDT: Colaboração em Tempo Real](/blog/rust-local-first-crdt-colaboracao-2026/)
- [Observabilidade com OpenTelemetry em Produção](/blog/rust-opentelemetry-producao-2026/)
- [Testes de Propriedade e Fuzzing em Rust](/blog/rust-proptest-fuzzing-property-based-testing-2026/)
- [Rust vs C++ em 2026](/blog/rust-vs-cpp-2026/)
- [Axum — Framework Web em Rust](/ecossistema/axum/)
- [Tutorial de API REST com Axum](/tutoriais/api-rest-axum/)

**Veja também nossos sites parceiros:**
- <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go Brasil</a> — comparando estratégias de núcleo compartilhado entre Rust e Go via gomobile
- <a href="https://python.dev.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'python.dev.br' })">Python Brasil Dev</a> — do Kivy e BeeWare ao núcleo Rust via PyO3 e UniFFI
- <a href="https://kotlin.dev.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'kotlin.dev.br' })">Kotlin Brasil Dev</a> — Kotlin Multiplatform e quando combinar com um núcleo Rust
