Modelos de Arquitetura de GUI em Rust
Todo framework de GUI desktop em Rust responde a uma pergunta fundamental de arquitetura antes de qualquer outra: a UI é reconstruída do zero a cada frame, ou ela persiste como uma estrutura de dados que muda incrementalmente? Essa única decisão - modo imediato versus modo retido - explica mais sobre como egui, iced e Tauri diferem do que qualquer comparação de recursos de seus widgets.
Esta página é a âncora conceitual para a seção GUI Desktop. A página de noções básicas percorre o código em cada framework; esta página explica por que esse código parece tão diferente dependendo do framework de origem, para que as páginas mais aprofundadas sobre egui, iced, Tauri e estado e arquitetura façam sentido como variações de um tema comum, em vez de três APIs não relacionadas.
Resumo
- Frameworks de GUI em Rust se dividem em um eixo de arquitetura - o modo imediato reconstrói a UI a cada frame a partir do estado atual, o modo retido mantém uma árvore de widgets persistente e a atualiza em resposta a eventos discretos.
- Por Que Importa: Essa escolha determina como você pensa sobre o estado, quão testável é sua lógica de UI, como o framework se comporta sob carga e o quanto a UI parece "nativa" versus "ferramenta".
- Conceitos Chave: modo imediato, modo retido, árvore de widgets, Modelo-Atualização-Visualização (arquitetura Elm), WebView, comparação (diffing).
- Quando Usar: Modo imediato para ferramentas internas, overlays de depuração e editores; modo retido/estilo Elm para aplicativos nativos voltados ao consumidor; baseado em WebView (Tauri) quando uma equipe já possui habilidades de UI web ou deseja um binário pequeno com um ecossistema frontend rico.
- Limitações / Trade-offs: O modo imediato troca alguma eficiência de CPU e polimento de acessibilidade por simplicidade; o modo retido troca alguma sobrecarga arquitetural por testabilidade e desempenho nativo; abordagens WebView trocam uma pilha Rust pura por ferramentas web e risco de interoperação JS.
- Tópicos Relacionados: programação reativa de UI, a arquitetura Elm, janelas nativas (
winit), limites de segurança de IPC.
Fundamentos
GUI de Modo Imediato (IMGUI) significa que toda a árvore de widgets é reconstruída a cada frame, diretamente do estado atual da sua aplicação. Não há um grafo de objetos persistente de botões e rótulos na memória entre os frames; em vez disso, sua função update é executada 60 vezes por segundo (ou sempre que uma repintura é solicitada), e cada execução diz "desenhe um botão aqui, um slider ali" com base nos valores atuais. egui é a principal biblioteca de modo imediato em Rust, emparelhada com a integração de janelas eframe.
GUI de Modo Retido é o modelo mais tradicional familiar de toolkits desktop: o framework mantém uma árvore persistente de objetos de widgets, e seu código muta essa árvore (ou uma descrição dela) em resposta a eventos, deixando o framework descobrir o que realmente mudou e repintar apenas a diferença. iced implementa isso através da arquitetura Elm - um Model puro, uma função update que transforma mensagens em novo estado do modelo, e uma função view que transforma o modelo em uma descrição de widget, que iced então compara com a do frame anterior.
Um terceiro caminho contorna a distinção imediato/retido inteiramente. Tauri não implementa seu próprio modelo de renderização - ele embuti o WebView nativo do sistema operacional (WebView2 no Windows, WKWebView no macOS, WebKitGTK no Linux) e deixa HTML, CSS e JavaScript padrão lidarem com a renderização, enquanto Rust é o proprietário do backend: acesso ao sistema de arquivos, integração com o SO e qualquer coisa sensível à segurança, comunicada através de uma fronteira de IPC. Entender essas três formas - reconstruir-a-cada-frame, árvore-persistente-com-comparação, e delegar-para-o-motor-do-navegador - é o modelo mental mais útil para escolher entre as opções de GUI desktop em Rust.
Mecânicas e Interações
No egui, um frame começa quando a camada de janelas (winit, encapsulada por eframe) entrega eventos de entrada e chama seu método App::update com uma referência mutável à sua struct de aplicação e o egui::Context atual. Dentro dessa chamada, você emite declarações de sensação imperativa como ui.button("Save"), e egui internamente rastreia quais IDs de widget foram tocados neste frame, calcula o layout e pinta. Como toda a árvore é reconstruída a cada frame, não há etapa de comparação para seu código raciocinar - o estado vive diretamente em sua struct, e a "UI" é apenas uma projeção dela calculada a cada vez.
O iced de modo retido inverte esse fluxo. Sua função view(&self) -> Element<Message> é pura: dado o modelo atual, ela retorna uma descrição de como a UI deve parecer, e o runtime do iced compara essa descrição com a anterior para calcular o conjunto mínimo de mudanças visuais. A interação do usuário não muta nada diretamente - ela produz um valor Message, que o iced roteia para sua função update(&mut self, message: Message), que é o único lugar onde o estado muda. Esse loop unidirecional (Modelo -> Visualização -> Mensagem -> Atualização -> Modelo) é deliberadamente reminiscente da arquitetura da linguagem Elm, e é por isso que o código do iced se lê mais como um redutor do que um script de UI imperativo.
// iced: view é puro (modelo -> descrição), update é o único ponto de mutação
fn update(count: &mut i32, message: Message) {
match message {
Message::Increment => *count += 1, // o ÚNICO lugar onde o estado muda
}
}As mecânicas do Tauri são diferentes em tipo, não apenas em grau. O WebView possui todo o loop de renderização usando o pipeline de layout e pintura do próprio motor do navegador, que Rust nunca toca diretamente. O papel do Rust é expor funções #[tauri::command] que o frontend JavaScript invoca através de um canal IPC serializado, e manter State<T> que sobrevive entre essas invocações. Isso significa que a pergunta imediato/retido é respondida inteiramente no lado JavaScript (comparação de DOM virtual do React, reatividade compilada do Svelte, ou manipulação simples do DOM) - a preocupação arquitetural do Rust no Tauri é a fronteira de segurança do IPC, não o loop de renderização.
Considerações Avançadas e Aplicações
As características de desempenho divergem acentuadamente em escala. O egui de modo imediato recomputa o layout para cada widget visível a cada frame por design, o que é barato para dashboards e inspetores com algumas centenas de widgets, mas pode se tornar um custo real com telas profundamente aninhadas de mil widgets - a correção é quase sempre fazer computação cara em uma thread de background e obter resultados via canal, não lutar contra o loop de frame em si. O iced de modo retido paga um custo de comparação em vez disso, que escala melhor com UIs grandes e majoritariamente estáticas (uma tela de configurações com widgets raramente alterados), mas adiciona sobrecarga arquitetural para visualizações genuinamente rápidas (uma forma de onda ao vivo) onde o modelo "apenas redesenhe tudo" do modo imediato é frequentemente mais simples e igualmente rápido na prática.
Acessibilidade é um eixo subestimado. Toolkits de modo retido e UIs baseadas em WebView geralmente têm um caminho muito mais fácil para suporte a leitores de tela, porque a plataforma subjacente (APIs de widgets nativos ou a árvore de acessibilidade do navegador) já entende a estrutura persistente da UI. Frameworks de modo imediato reconstroem tudo a cada frame, o que torna mais difícil expor uma árvore de acessibilidade estável para APIs do SO - um trade-off genuíno e honesto, não uma lacuna temporária.
A arquitetura de estado também se compõe de forma diferente à medida que um aplicativo cresce. No egui, o estado naturalmente se acumula em uma grande struct App, a menos que você a divida deliberadamente, pois não há fronteira imposta pelo framework entre "estado da UI" e "estado do domínio". No iced, a arquitetura Elm força essa fronteira desde o primeiro dia - seu Model é seu estado, Message é seu vocabulário de eventos - o que custa mais cerimônia inicial, mas compensa em testabilidade, já que update é uma função pura que você pode testar unitariamente com valores simples e sem nenhuma janela.
| Abordagem | Força | Fraqueza | Melhor Ajuste |
|---|---|---|---|
| Modo imediato (egui) | Modelo mental simples, iteração rápida, fácil integração com thread de background | Recomputa a cada frame, acessibilidade mais fraca, dispersão de estado em escala | Ferramentas internas, overlays de depuração, inspetores de engine de jogos |
| Modo Retido/Elm (iced) | update puro testável, comparação mantém UIs grandes eficientes, fronteiras de estado impostas | Mais arquitetura inicial, menos flexível para visualizações em rápida mudança | Aplicativos nativos voltados ao consumidor, equipes que gostam de fluxo unidirecional estilo Redux |
| WebView (Tauri) | Ecossistema web completo para UI, binário pequeno vs. Electron, Rust detém APIs sensíveis à segurança | Modelo de renderização vive fora do Rust, fronteira IPC deve ser cuidadosamente validada | Equipes com fortes habilidades de frontend web querendo empacotamento nativo |
Conceitos Errôneos Comuns
- "Modo imediato significa que a UI é redesenhada na GPU a cada frame, não importa o quê." - Não exatamente; egui ainda rastreia se a entrada ou o estado realmente mudaram e pode pular a repintura quando nada a solicita, "imediato" descreve a reexecução do código do widget, não necessariamente trabalho contínuo na GPU.
- "Modo retido é sempre mais rápido." - É mais rápido para UIs majoritariamente estáticas, mas a própria etapa de comparação tem um custo, e para UIs que mudam a cada frame de qualquer maneira (dados ao vivo, animações), o caminho mais simples do modo imediato pode vencer.
- "Tauri é apenas Electron com passos extras." - A arquitetura é significativamente diferente: Tauri usa o WebView já instalado do sistema operacional em vez de empacotar um runtime Chromium inteiro, que é a fonte de seus binários muito menores.
- "Você tem que escolher um framework para o seu aplicativo inteiro." - Configurações híbridas são comuns, mais visivelmente
bevy_egui, que sobrepõe uma UI de depuração de modo imediato sobre o próprio grafo de cena de modo retido de uma engine de jogos. - "A arquitetura Elm é exclusiva do iced." - É um padrão geral (fluxo de dados unidirecional, funções de atualização puras) que aparece bem além do Rust, iced simplesmente a implementa nativamente para GUIs desktop.
FAQs
Qual é a diferença em uma frase entre modo imediato e modo retido?
O modo imediato reconstrói toda a descrição do widget a partir do estado a cada frame; o modo retido mantém uma árvore de widgets persistente e atualiza apenas o que mudou.
Por que egui se autodenomina "modo imediato" se ele ainda rastreia IDs de widgets entre os frames?
Ele rastreia IDs para coisas como foco e estado de animação, mas seu código de aplicação reemite toda a descrição da UI a cada frame em vez de mutar um grafo de objetos persistente.
A comparação do iced é semelhante ao DOM virtual do React?
Conceitualmente sim - ambos computam uma nova descrição de UI a partir do estado e a comparam com a anterior para minimizar o trabalho real de renderização.
Por que o binário do Tauri é menor que o do Electron?
Tauri embuti o WebView já instalado do sistema operacional em vez de empacotar um runtime Chromium inteiro dentro do aplicativo, que é de onde vem a maior parte do tamanho do Electron.
Posso testar a lógica da minha UI sem abrir uma janela?
No iced, sim - update é uma função pura de modelo e mensagem, então você pode chamá-la diretamente em um teste unitário. No egui, é mais difícil porque o código da UI é executado dentro do callback update(ctx, ...) vinculado ao loop de frame.
Por que engines de jogos favorecem UI de modo imediato para ferramentas de depuração?
Overlays de depuração mudam constantemente a cada frame de qualquer maneira, então o modelo "apenas redesenhe" do modo imediato corresponde à carga de trabalho, e bevy_egui se integra perfeitamente a um loop de renderização existente.
O modo imediato significa que não posso ter animações suaves?
Não - egui suporta animação e solicita repinturas ao animar, mas o estado da animação tem que viver em algum lugar explícito (geralmente na memória do Context) já que não há um objeto de widget persistente para mantê-lo implicitamente.
Uma arquitetura é mais "com cara de nativo" do que as outras?
Toolkits de modo retido com widgets específicos da plataforma tendem a ter a aparência mais nativa; egui renderiza deliberadamente sua própria aparência consistente entre plataformas em vez de usar widgets do SO; a aparência do Tauri depende inteiramente da UI web construída dentro dele.
Por que a acessibilidade difere tanto entre esses modelos?
Leitores de tela dependem de uma estrutura de UI persistente e consultável, que toolkits de modo retido e a árvore de acessibilidade do navegador já fornecem, enquanto frameworks de modo imediato precisam reconstruir e expor essa estrutura a cada frame.
O que acontece com a arquitetura de estado à medida que um aplicativo egui cresce?
O estado tende a se acumular em uma grande struct de aplicação, a menos que você a divida deliberadamente em módulos, já que egui não impõe uma fronteira de estado/UI da mesma forma que a arquitetura Elm faz.
A fronteira IPC do Tauri é uma preocupação de desempenho ou de segurança?
Principalmente segurança - cada comando é um limite de confiança entre o JavaScript frontend não confiável e o código Rust privilegiado, então a validação de entrada é mais importante aqui do que a taxa de transferência bruta de IPC para a maioria dos aplicativos.
Devo escolher o framework com base em 2D vs 3D, ou com base nesta questão de arquitetura?
Com base nesta questão primeiro - a tecnologia de renderização (wgpu, WebView) é frequentemente compartilhada ou substituível, mas a divisão imediato/retido/WebView determina como todo o seu codebase é estruturado e testado.
Relacionados
- Noções Básicas de GUI - exemplos práticos iniciais em todos os três frameworks
- egui - o framework de modo imediato em profundidade
- iced - o framework de modo retido, estilo Elm, em profundidade
- Tauri - a arquitetura baseada em WebView em profundidade
- Estado e Arquitetura - escalando o gerenciamento de estado dentro de qualquer modelo
Versões da Stack: Esta página é conceitual e não está vinculada a uma versão específica da stack.