Se eu reescrevesse o Rust
(josephg.com)- O ponto de partida é a percepção de que, apesar da segurança de memória, do desempenho, dos tipos algébricos e das vantagens do Cargo, a evolução da linguagem no canal estável desacelerou e ela parece “um produto de primeira geração ainda inacabado”
- Depois que a comunidade cresceu, a tomada de decisão baseada em consenso virou um gargalo, e continuam se acumulando recursos como corrotinas que até foram implementados, mas não podem ser usados por muito tempo no Rust estável
- A hipotética “seph edition” manteria o ecossistema existente de Rust e Cargo, mas permitiria breaking changes para experimentar sistemas de efeitos, permissões em tempo de compilação, borrow de campos de struct,
comptimee mais - Para reduzir riscos na cadeia de suprimentos, seria preciso anexar capabilities a recursos sensíveis como escrita em arquivos, rede, FFI e
unsafe, exigindo que o chamador permita isso explicitamente - A maioria das propostas não é compatível com o Rust atual e, mesmo ao adicionar só capabilities, surgem novas condições de falha que quebram a compatibilidade semver, exigindo uma nova edition ou um fork do compilador
Por que Rust parece “um produto de primeira geração”
- No começo, Rust se apresentou como uma linguagem muito atraente graças aos tipos algébricos, à segurança de memória sem perda de desempenho e a um gerenciador de pacotes moderno
- Depois de cerca de 4 anos de uso, fica a sensação de que a linguagem permanece “sempre um pouco aquém do ideal”
- A queixa central é que a frequência de entrada de novos recursos no Rust estável caiu, e o ritmo de evolução da linguagem também desacelerou bastante
- O Rust unstable book lista cerca de 700 recursos unstable, e uma parte significativa deles são mudanças na biblioteca padrão
- Coroutines é um recurso cujo RFC tem 7 anos e que já foi implementado no compilador, mas ainda não pode ser usado por quem depende do Rust estável
- O processo de RFC do Rust parece um lugar onde boas ideias ficam paradas por muito tempo
- A discussão sobre melhorias em
Mutexé citada como exemplo: 25 pessoas deixaram mais de 200 comentários ao longo de 2 anos sem chegar a uma conclusão clara
- A discussão sobre melhorias em
A ideia da hipotética “seph edition”
- Imagina-se fazer um fork do compilador, mas manter o código Rust existente como está e adicionar uma Rust edition separada chamada “seph”
- Nessa edition, seria permitido fazer breaking changes, mas, se o compilador ainda conseguir compilar o Rust da linha principal, continuaria sendo possível usar os crates existentes do Cargo
- Há cinco grandes eixos de mudança desejados
- traits de função e sistema de efeitos
- capabilities em tempo de compilação
- redesenho de
Pin, move e borrow de structs comptimeno estilo Zig- pequenos ajustes de sintaxe e da biblioteca padrão
Traits de função e sistema de efeitos
- Rust tem traits para structs, mas a proposta é que funções também possam receber traits/effects mais ricos
- As propriedades que uma função poderia ter são resumidas assim
- possibilidade de panic
- tamanho de stack fixo ou não
- se executa até o fim ou se faz
yieldouawait - no caso de coroutine, o tipo da continuation
- se é uma função pura
- se executa código
unsafeindiretamente - garantia de terminação
- Se os parâmetros e o tipo de retorno da função fossem expostos como associated types da própria função, passaria a ser possível fazer referências de tipo hoje impossíveis no Rust estável
- O exemplo é um código que nomeia diretamente o tipo de retorno de uma função como
some_iter::Outputpara colocá-lo em um campo de struct - Em casos como o kernel do Linux, onde é preciso garantir que certo bloco de código jamais dê panic, uma marcação como
#[disallow(Panic)]poderia verificar a possibilidade de panic inclusive em chamadas recursivas - O compilador já lida com traits de função como
Fn,FnOnceeFnMut, mas a avaliação é que, na forma atual, eles são pobres demais - Como referência mais detalhada, é indicado o artigo e a apresentação de Yoshua Wuyts sobre sistema de efeitos
Reduzindo risco de cadeia de suprimentos com capabilities em tempo de compilação
- A maioria dos projetos em Rust traz muitos crates de terceiros, e até um crate utilitário pequeno pode criar risco de cadeia de suprimentos por meio de uma atualização maliciosa
- Assim como
unsafeexige opt-in explícito para questões de segurança de memória, recursos sensíveis ligados a sistema de arquivos, rede, FFI e raw pointers também deveriam exigir autorização explícita - A proposta é anexar marker tags às funções sensíveis da biblioteca padrão
- Ex.:
std::fs::write(path, contents)grava um arquivo em um caminho arbitrário, então receberia uma tag como#[cap(fs_write)] - O compilador faria automaticamente o taint de toda a árvore de chamadas que invoca essa função
- Ex.:
- Se um crate de terceiros precisar da capability
fs_write, o chamador teria de autorizá-la explicitamente noCargo.tomlou com uma annotation no ponto de chamada - Se isso não for permitido, o compilador poderia emitir um erro dizendo que
foo::do_stuff()escreve no sistema de arquivos local, mas o cratefoonão foi confiado para essa capability - Muitos crates utilitários como
human-sizeouserdenão precisariam de capabilities especiais, então, mesmo que o autor adicione código malicioso, operações de escrita em arquivo ou de rede poderiam ser bloqueadas na compilação - Como alternativa, também existe a ideia de mudar APIs sensíveis para que recebam um parâmetro
Capabilityseparado- Ex.:
std::fs::writepassaria a exigir um valorFsWriteCapability - A criação de objetos
Capabilityseria restrita para só poder acontecer no crate raiz
- Ex.:
- Esse modelo aumenta o boilerplate, mas é mais flexível, e algo parecido também seria necessário para scripts
build.rse blocosunsafe - Mesmo que o crates.io fosse comprometido e
serdepassasse a incluir código de cryptolocker, no modelo de capability isso geraria erro de compilação antes de esse código rodar nas máquinas de vários desenvolvedores ou ser incluído no binário
Redesenho de Pin, move e borrow de campos de struct
Piné visto como um hack complexo para contornar uma lacuna no borrow checker, um tipo de band-aid surgido da tentativa de preservar compatibilidade retroativa- O que realmente seria necessário estaria mais perto de um marker trait
Movepara marcar tipos que podem se mover - No Rust atual não existe trait
Pin; existemUnpine!Unpin, e essa forma de dupla negação torna o conceito ainda mais difícil - Como
Pinsó se aplica a tipos de referência, isso acaba espalhando código comBoxpor toda parte- Como exemplos, são citadas bibliotecas auxiliares como wrapper de stream do Tokio,
ouroboros,async-traiteself_cell
- Como exemplos, são citadas bibliotecas auxiliares como wrapper de stream do Tokio,
- Funções que recebem valores pinned, como
Future::poll(self: Pin<&mut Self>, ..), também ficam mais complexas, e passam a exigir crates separados para lidar com projection - Dentro das funções, o borrow checker gerencia variáveis em estados como “owned”, “borrowed” e “mutably borrowed”, mas esses estados não ficam visíveis diretamente para o programador
- Em
async fn, o compilador cria uma struct oculta para armazenar o estado de suspensão, e nesse processo pode surgir uma situação em que um campo faz borrow de outro campo - Rust não tem sintaxe para expressar que um campo de struct está em estado borrowed, nem para expressar diretamente lifetimes entre campos
- A direção proposta é estender o borrow checker para permitir escrever explicitamente o borrow de campos de struct
- Ex.: uma sintaxe de “borrow local” como
y: &'Self::x Vec<usize> - O compilador saberia que
xestá borrowed e aplicaria as mesmas restrições usadas para variáveis borrowed dentro de funções
- Ex.: uma sintaxe de “borrow local” como
- Essa sintaxe também poderia servir para lidar com self-referential structs ou casos como uma AST com
source: Stringeast_nodes: Vec<&'Self::source str> - Structs com campos borrowed não poderiam se mover, então deixariam de implementar
Move; além disso, considera-se até um traitMoverpara mover o valor com segurança por conta própria
comptime no estilo Zig e o problema das macros em Rust
- O compilador de Rust parece, na prática, lidar com várias linguagens ao mesmo tempo: Rust, a linguagem de macro de Rust e ainda proc macros
- O ponto central é que o próprio Rust é bom, mas a linguagem de macro não é
- O comptime do Zig funciona com o compilador executando partes do código em tempo de compilação por meio de um pequeno interpretador
- Funções, parâmetros,
ife loops podem ser marcados como código de compilação, enquanto o código non-comptime é emitido para o programa final - O estudo de caso de
std printdo Zig mostra uma string de formatação passada como parâmetro comptime e analisada em um loopcomptimepara gerar o código de saída - A implementação de
println!()no Rust chama funções internas comoformat_args_nl, e a suspeita é que essa função esteja hardcoded no compilador - A crítica continua dizendo que parece que até os próprios autores do compilador Rust não querem usar a linguagem de macros de Rust
Pequenas mudanças de sintaxe e da biblioteca padrão
- Há o desejo de corrigir o fato de
Range<T>implementarCopyquandoT: Copy - Problemas de derive com associated types também são apontados como alvo de correção
- Issue relacionada: derive with associated types
- Exemplo: Playground
- Existe a proposta de que expressões
if letpassem a suportar logical AND- Forma desejada:
if let Some(x) = some_var && some_expr { } - Hoje, o contorno é fazer pattern matching sobre uma tupla como
(some_var, check_foo()), mascheck_foo()é executado mesmo quandosome_varéNone, diferentemente do short-circuit de umifnormal - Exemplo: Playground
- Forma desejada:
- A ergonomia de raw pointers também é vista como algo que precisa melhorar
- Em referências dá para usar
myref.x, mas em pointers é preciso escrever(*myptr).xou(*(*myptr).p).y - A posição defendida é que código
unsafedeveria ser o mais fácil possível de ler e escrever
- Em referências dá para usar
- Também há a proposta de que tipos de coleção embutidos recebam explicitamente um
Allocatorno construtor, em vez de depender de um allocator global asynctambém precisaria de melhorias, mas ficou para um texto separado porque o assunto é grande demais
Compatibilidade e viabilidade
- A maioria das propostas não é compatível com o Rust atual
- Até a adição de capabilities de segurança exigiria uma nova Rust edition, porque introduz novas condições de falha para crates e quebra compatibilidade semver
- Alguns anos atrás talvez valesse escrever um RFC, mas a ideia é evitar passar por um longo processo de comentários no GitHub para acabar com mais uma ideia que nunca se concretiza
- No fim, volta-se à imaginação de fazer um fork do compilador, mas permanece a limitação prática de já haver projetos demais para tocar
1 comentários
Opiniões no Hacker News
Ao contrário da ideia de que o processo de RFCs do Rust é um cemitério de boas ideias, acho que a equipe central do Rust está no caminho certo ao tornar difícil adicionar novos recursos à linguagem de programação
É preciso evitar que a superfície da linguagem fique inchada, inconsistente e imprevisível. Eu também gostava de Swift no começo, mas acabei desistindo à medida que foram se acumulando coisas como nomes de funções redundantes como
isMultiple(of:), regras de parsing de chaves para o SwiftUI, regras de tipos por referência/valor e mutabilidade, e notação abreviada de argumentos de closures. Boas ideias são comuns, então gostaria que o Rust fosse mantido o mais enxuto possívelPor exemplo, é possível usar uma função que retorna
impl Traite uma struct pode ter campos arbitrários, mas não é possível colocar em um campo de struct um valor retornado comoimpl Trait. Isso porque não dá para dar um nome ao tipo.if a && bfunciona, eif let Some(x) = xtambém, masif let Some(x) = x && bnão. Gostaria que esse tipo de coisa fosse corrigido. Em termos de linhas de código do compilador, o Rust ficaria maior, mas, em termos de complexidade para aprender e usar, essas lacunas de recursos tornam a linguagem mais complexa.Pinnão foi um grande trabalho para implementar na biblioteca padrão, mas não é nem um pouco um recurso enxuto, e impõe uma carga cognitiva enorme. Prefiro um verificador de empréstimos complexo e código Rust fácil de ler a um compilador simples e uma linguagem difícil de usarMesmo que 100% das ideias na fila fossem aceitas e eventualmente trabalhadas, pareceria assim por causa da diferença entre a velocidade de entrada e a de saída. Se você quer ficar à frente dos autores de RFCs, é melhor não escrever uma RFC; crie uma implementação pronta para produção, com documentação e testes, que possa ser mesclada de forma limpa na árvore
Não dá para usá-las nem como const generic de outro tipo, nem como tamanho de array. Se você só precisa de um valor, basta definir uma função e retorná-lo, então, do jeito que está, elas não são muito úteis. Se houvesse suporte adequado, seria possível eliminar crates auxiliares como
generic_arrayetypenumem bibliotecas de criptografia. Ainda assim, concordo que a equipe do Rust deve ser cautelosa ao adicionar recursosA situação das dependências é bem grave, mas parece que pouca gente quer admitir. Um exemplo recente que vi foi o crate cargo-watch
No fundo, é um app simples que monitora alterações em arquivos e roda o compilador de novo, e a implementação nem chega a 1.000 linhas. Mas, ao vendorizarmos as dependências, isso cresce para quase 4 milhões de linhas de código Rust espalhadas por mais de 8.000 arquivos. É demais para um simples monitor de arquivos
https://crates.io/crates/cargo-watch
C/C++ é quase o único caso, entre linguagens amplamente usadas, em que um gerenciador de pacotes no estilo npm não se popularizou; por isso, a maioria das bibliotecas costuma ser autocontida ou ter poucas dependências, muitas vezes opcionais.
efswé um monitor de sistema de arquivos em C++ com 7.000 linhas e sem dependências. As bibliotecas single-header do mundo de programação de jogos, comostb_*,cgltf, e o Dear ImGui, foram das mais agradáveis que já usei. Acho que, em um novo gerenciador de pacotes, proibir dependências transitivas poderia ser um ganho geral. Dependências de bibliotecas grandes poderiam ser instaladas diretamente pelo usuário, expostas via callbacks ou transformadas em recursos padrãohttps://github.com/SpartanJ/efsw
https://github.com/nothings/stb
https://github.com/jkuhlmann/cgltf
https://github.com/ocornut/imgui
Acho que monitores de arquivos, especialmente quando dão suporte a várias plataformas, nunca são tão simples assim
Parte de
cargo watchtraz uma biblioteca wrapper para a API Win32, que é um binding gerado automaticamente para chamadas Win32, então não tem como não ser enorme. As bibliotecas padrão da maioria das linguagens também têm milhões de linhas, e os apps usam só uma parte delas. O Boost do C++ também é monstruosamente grande, mas os desenvolvedores só usam algumas extensões. Na verdade, é melhor ter um número menor de dependências grandes mantidas e usadas por muita gente do que um inferno no estilo npm, em que um pacote comoisOddouis evenpode quebrar o ecossistema; Rust, em geral, está mais perto do primeiro casoA maior parte das dependências de
cargo-watchparece vir de três requisitos diretos:clap,cargo_metadataewatchexec.clappuxa muitos elementos de CLI específicos de plataforma, ecargo_metadatainevitavelmente traz bastante coisa da famíliaserde.watchexectem margem para melhoria: ele depende decommand-group, mantido pela mesma organização, que exige Tokio incondicionalmente. O problema maior é que não é fácil corrigir o excesso de dependências de outros crates. Se houver um crate preso a umwatchexecantigo, às vezes dá para ajustar comCargo.lock, mas normalmente é difícil, e é preciso contornar com[patch]. Seria bom ter uma forma fácil de definir um “stand-in” para substituir um crate de uma versão específica, mas isso provavelmente seria um grande problema de pesquisa para gerenciadores de pacotes existentesRust já não é mais uma linguagem nova e empolgante; está na fase de caminhar rumo a uma adoção ampla. É natural e saudável que o desenvolvimento de funcionalidades desacelere
Neste momento, erros de design são muito mais prejudiciais do que uma velocidade baixa. O motivo de Rust ser interessante não são recursos legais, mas o fato de ser uma nova categoria de linguagem com segurança de memória, sem garbage collection e pronta para produção. Vê-la ser adotada de fato em lugares importantes é mais interessante do que tornar a linguagem melhor, e isso fica mais fácil quando há confiança de que ela é conduzida com cautela
Operador spread, funções geradoras,
async, arrow functions,leftpad, novoDateetc.; a lista de recursos importantes não acaba. JS é muito mais antiga que Rust e muito mais amplamente usada, e tem várias implementações em produção que precisam implementar todos os novos recursos de forma compatível. Houve um período de estagnação por volta do ES5, mas a diferença parece estar no fato de o comitê de padrões do ECMAScript ter entrado nos eixosComo os Rustaceans são obcecados por reescrever tudo em Rust, eu sinceramente achei que um texto sobre reescrever Rust seria uma piada de metassátira
É meio estranho reclamar primeiro da lentidão nas decisões e depois listar recursos que não foram estabilizados por motivos que têm pouca relação com tomada de decisão
Por exemplo, corrotinas estão bloqueadas porque há casos de borda difíceis de resolver corretamente. Não existe uma implementação completa dentro do compilador que seja “só ligar”; é uma implementação inacabada que funciona em muitos casos, mas não pode ser ativada na versão estável. Os traits de função também estão em um estado em que foi decidido explicitamente não estabilizá-los na forma atual, por várias razões técnicas e por interações com recursos futuros. Se voltássemos no tempo, haveria partes que seriam projetadas de outra forma, mas a maioria está ligada a decisões do começo do Rust, quando a equipe e os recursos eram muito menores. Pode haver a ideia de que, se hoje é possível fazer escolhas melhores, deveríamos criar uma ruptura ao estilo Rust 2.0, mas como o desastre da migração do Python 2 para o 3 foi grande demais, muita gente acha melhor conviver com algumas arestas. Olhando a newsletter semanal do Rust, aprovações de RFCs e decisões de estabilização são tratadas toda semana. Há casos que demoram demais, mas problemas de pessoas, coordenação e falta de tempo muitas vezes são mais difíceis de resolver do que problemas técnicos
Este texto também deveria ser lido junto com a resposta de Josh Triplett no Reddit. Um dos exemplos centrais do texto, Mutex, está simplesmente errado
https://old.reddit.com/r/rust/comments/1fpomvp/rewriting_rus...
Correção: o mesmo comentário também está aqui
https://news.ycombinator.com/item?id=41655268
A primeira coisa que senti ao aprender Rust foi que ele parecia já incluir praticamente todos os recursos imagináveis
Isso não quer dizer que a equipe do Rust nunca tenha recusado algo, mas, ainda assim, as pessoas querem mais recursos. Alguns são legítimos, mas outros parecem seguir a lógica de que um recurso que só 2% dos desenvolvedores vão usar precisa necessariamente entrar em uma linguagem que só 1% dos desenvolvedores entende. Uma linguagem complexa não precisa ficar ainda mais complexa. Zig é mais simples e provavelmente mais rápida, e tem muito menos drama de comunidade. Gostaria que mais financiamento fosse para o Zig
Rust não tenta colocar todos os recursos possíveis. Mas, se você não souber claramente que problema coisas como GAT ou TAIT resolvem, pode parecer assim. Zig pode ser uma boa linguagem moderna, mas, se o objetivo é segurança de memória, ela não é uma opção
Por exemplo, a interação entre
Pine futures adiciona uma complexidade absurda à linguagem, e acho que parte dela é desnecessária. Eu gostaria que existisse uma linguagem parecida com Rust sem nenhumPin. Também parece haver maneiras de simplificar o borrow checker tanto na sintaxe quanto na implementação, embora eu ainda não tenha pensado nisso de forma concreta. Hoje seria difícil mudar sem fazer um fork da linguagem, mas Rust não será a última linguagem a usar borrow checker. Espero que a próxima geração consiga melhorar bastante sem criar uma linguagem maiorVejo dramas relacionados ao Rust, mas geralmente eles vêm de quem resiste ao uso ou à adoção de Rust. Como a recente confusão em torno de Rust for Linux. Não me parece algo comum dentro da própria comunidade, embora eu possa ter perdido alguma coisa. Zig é excelente, mas ainda não está pronto para produção
O autor do comentário linkado analisou amplamente os primitivos de sincronização de várias linguagens e depois reescreveu primitivos de sincronização como
MutexeRwLockdo Rust para que usassem diretamente os recursos primitivos do SO subjacente em cada sistema operacional principalUsando coisas como
futexno Linux, ele os tornou mais rápidos, menores e melhores no geral; nesse processo, basicamente também escreveu um livro sobre programação paralela em Rust. Ele também é útil para programação paralela fora de Rusthttps://www.oreilly.com/library/view/rust-atomics-and/978109...
Também não é que tenham passado 7 anos só brincando com corrotinas. Foram adicionadas funções assíncronas, traits que podem conter funções assíncronas e vários recursos necessários para padronizar
AsyncWriteeAsyncRead; no nightly também há uma implementação de geradores. Ainda se discute se vale assumir a complexidade de corrotinas totalmente gerais ou parar nos geradores. Alguns recursos, comoAsyncIterator, avançam devagar, mas há muito trabalho ativo. É sempre interessante ver um lado dizendo que a linguagem é lenta demais e o outro reclamando que ela é rápida demaisTraits de função e sistemas de efeitos também tiveram recentemente uma grande exploração de design, e há o desejo de encontrar uma solução para não precisar escrever funções várias vezes para cada combinação de
async,tryeconst. Sandboxing de crates maliciosos não é um problema de nível da linguagem; exige uma combinação de verificadores e sandboxes em tempo de execução, e componentes WebAssembly parecem mais promissores. Ainda assim, há muito interesse em capabilities em tempo de compilação, como a escolha de alocador ou runtime assíncrono e a suposição de plataformas de 64 bits; já o sandboxing de proc macros é desejado não para impedir ações maliciosas, mas para viabilizar cache corretoStructs autorreferenciais não são um problema de sintaxe, mas algo muito difícil para o borrow checker tratar. Empréstimos parciais já são suportados em capturas de closures, mas a questão central é como manter uma semântica estável de versionamento quando isso é exposto em APIs públicas. Uma abordagem como “borrow groups” nomeados parece promissora. Também há trabalho em várias direções no lado de
comptime, e uma RFC para fortalecermacro_rulesfoi escrita recentemente. OimpldeRangejá está em andamento, atrelado a mudanças incompatíveis por meio de uma edição. A combinação deif letcom AND lógico tem um recurso instável e está perto da estabilização. Também houve várias propostas para melhorar a sintaxe de acesso a campos por ponteiro, mas continua em aberto se ampliar ainda mais a superfície da linguagem traz mais benefício ou prejuízo. Graças às edições do Rust, muita coisa pode ser mudada se houver um design suficientemente convincente. Os 700 recursos instáveis são um problema real, e será necessário um grande esforço para limpar aqueles com baixa chance de estabilizaçãoMacros mais declarativas, delegação da lógica de crates
-syspara bibliotecas compartilhadas ecfg(version)/cfg(accessible)reduziriam bastante a necessidade de implementações por usuários, mas o runtime ainda permaneceria. Quanto mais penso nisso, mais o ACL do cackle parece uma boa forma extensível de rastrear trabalho em proc macros, scripts de build e código de runtime, além de auditar o uso da árvore de dependências. Ouvi dizer quecargo-redpentambém está evoluindo como ferramenta de auditoria de chamadas, mas imagino algo de nível mais alto, como o cacklehttps://github.com/cackle-rs/cackle
Entendo as reclamações de que Rust já é uma linguagem grande e que as pessoas não querem que ela cresça mais, mas manter a atual implementação de async meio crua não torna a linguagem menor nem mais simples. Só torna a linguagem pior. Quanto a empréstimos parciais, já seria bom se funcionassem ao menos dentro do mesmo crate, mesmo que não em APIs públicas. É um problema que aparece o tempo todo na programação real. Também não entendo por que o sandboxing de crates maliciosos seria impossível no nível da linguagem. Se uma função não tem código
unsafede terceiros na árvore de chamadas e não faz chamadas de sistema, então ela só consegue lidar com os argumentos já recebidos, variáveis locais e globais dentro do escopo. Se essa barreira puder ser reforçada e usada como limite de segurança, parece que o risco da cadeia de suprimentos de dependências poderia ser bastante reduzidoA missão do Rust sempre foi difícil desde o início: misturar desempenho, segurança e expressividade. Com a saída da Mozilla, ele perdeu o modo fundador, e a maior parte da equipe central original também saiu, então não surpreende que o progresso tenha ficado mais lento
Pessoalmente, acho melhor do que seguir pelo caminho errado
Se fosse para reescrever Rust, acho que eu iria na direção de reduzir recursos, em vez de acrescentar mais
Algo como o QBE faz em relação ao LLVM: entregar 70% do Rust com 10% do código. Talvez seja possível removendo macros e alguns recursos raramente usados
https://c9x.me/compile/
Seja com Rust ou qualquer outra coisa, é difícil saber o quanto isso é possível antes de realmente tentar