1 pontos por GN⁺ 2024-09-27 | 1 comentários | Compartilhar no WhatsApp
  • 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, comptime e 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 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
    • comptime no 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 yield ou await
    • no caso de coroutine, o tipo da continuation
    • se é uma função pura
    • se executa código unsafe indiretamente
    • 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::Output para 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, FnOnce e FnMut, 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 unsafe exige 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
  • Se um crate de terceiros precisar da capability fs_write, o chamador teria de autorizá-la explicitamente no Cargo.toml ou 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 crate foo não foi confiado para essa capability
  • Muitos crates utilitários como human-size ou serde nã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 Capability separado
    • Ex.: std::fs::write passaria a exigir um valor FsWriteCapability
    • A criação de objetos Capability seria restrita para só poder acontecer no crate raiz
  • Esse modelo aumenta o boilerplate, mas é mais flexível, e algo parecido também seria necessário para scripts build.rs e blocos unsafe
  • Mesmo que o crates.io fosse comprometido e serde passasse 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 Move para marcar tipos que podem se mover
  • No Rust atual não existe trait Pin; existem Unpin e !Unpin, e essa forma de dupla negação torna o conceito ainda mais difícil
  • Como Pin só se aplica a tipos de referência, isso acaba espalhando código com Box por toda parte
    • Como exemplos, são citadas bibliotecas auxiliares como wrapper de stream do Tokio, ouroboros, async-trait e self_cell
  • 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 x está borrowed e aplicaria as mesmas restrições usadas para variáveis borrowed dentro de funções
  • Essa sintaxe também poderia servir para lidar com self-referential structs ou casos como uma AST com source: String e ast_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 trait Mover para 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, if e 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 print do Zig mostra uma string de formatação passada como parâmetro comptime e analisada em um loop comptime para gerar o código de saída
  • A implementação de println!() no Rust chama funções internas como format_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> implementar Copy quando T: Copy
  • Problemas de derive com associated types também são apontados como alvo de correção
  • Existe a proposta de que expressões if let passem 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()), mas check_foo() é executado mesmo quando some_var é None, diferentemente do short-circuit de um if normal
    • Exemplo: Playground
  • 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).x ou (*(*myptr).p).y
    • A posição defendida é que código unsafe deveria ser o mais fácil possível de ler e escrever
  • Também há a proposta de que tipos de coleção embutidos recebam explicitamente um Allocator no construtor, em vez de depender de um allocator global
  • async també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

 
GN⁺ 2024-09-27
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ível

    • Sou o autor do texto original. Ao usar Rust, frequentemente há lacunas em que o recurso X e o recurso Y são suportados separadamente, mas não podem ser usados juntos
      Por exemplo, é possível usar uma função que retorna impl Trait e uma struct pode ter campos arbitrários, mas não é possível colocar em um campo de struct um valor retornado como impl Trait. Isso porque não dá para dar um nome ao tipo. if a && b funciona, e if let Some(x) = x também, mas if let Some(x) = x && b nã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. Pin nã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 usar
    • Escrever uma RFC é mais fácil do que implementá-la, e há mais pessoas capazes de escrever RFCs. Por isso, em qualquer nível de popularidade, a fila de RFCs inevitavelmente vai parecer um cemitério de boas ideias
      Mesmo 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
    • Concordo em geral, mas as constantes associadas genéricas em traits estão em um estado paradoxal: foram estabilizadas, mas não podem ser usadas como constantes de verdade
      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_array e typenum em bibliotecas de criptografia. Ainda assim, concordo que a equipe do Rust deve ser cautelosa ao adicionar recursos
    • Entre as linguagens de programação que já são em alguma medida mainstream, Rust está em um estado meio wagyu, então não se deve adicionar tanta gordura a ponto de ficar intragável
    • Boas ideias são, por definição, raras e valiosas
  • A 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

    • Quando é fácil criar dependências transitivas e existe uma cultura de “se há uma biblioteca, você deve usá-la”, isso inevitavelmente acontece
      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, como stb_*, 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ão
      https://github.com/SpartanJ/efsw
      https://github.com/nothings/stb
      https://github.com/jkuhlmann/cgltf
      https://github.com/ocornut/imgui
    • Hoje em dia, a selva de dependências é o modo comum de fazer as coisas. O melhor que um runtime pode fazer é aceitar isso, torná-lo o mais rápido e seguro possível e ampliar a biblioteca padrão para também dar suporte a projetos com poucas dependências
      Acho que monitores de arquivos, especialmente quando dão suporte a várias plataformas, nunca são tão simples assim
    • É um fenômeno natural e não tão assustador. Todo código fica em cima de uma cordilheira de dependências que contém muito mais funcionalidades do que ele próprio usa
      Parte de cargo watch traz 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 como isOdd ou is even pode quebrar o ecossistema; Rust, em geral, está mais perto do primeiro caso
    • No trabalho, removemos conscientemente várias dependências e reescrevemos coisas, mas, seja 1.000 linhas ou 4 milhões de linhas, ambas parecem estimativas imprecisas para a quantidade de código apropriada para este projeto
      A maior parte das dependências de cargo-watch parece vir de três requisitos diretos: clap, cargo_metadata e watchexec. clap puxa muitos elementos de CLI específicos de plataforma, e cargo_metadata inevitavelmente traz bastante coisa da família serde. watchexec tem margem para melhoria: ele depende de command-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 um watchexec antigo, às vezes dá para ajustar com Cargo.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 existentes
    • É bem provável que a maior parte das linhas venha dos crates da API do Windows gerados. Eles são notoriamente enormes
  • Rust 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

    • JavaScript também pode ser vista como estando em uma fase de vida parecida, mas, nos últimos 10 anos, recebeu um monte de ótimos recursos novos
      Operador spread, funções geradoras, async, arrow functions, leftpad, novo Date etc.; 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 eixos
    • Ada/SPARK já existem há muito tempo, então fico em dúvida se é mesmo uma nova categoria de linguagem. SPARK leva a segurança ainda mais longe, incluindo verificação formal
  • Como 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

    • Isso já aconteceu na pré-história. Originalmente, Rust foi escrito em OCaml, mas acabou sendo reescrito em Rust
    • Sou o autor do texto original. Essa era exatamente a referência pretendida no título
    • Eles querem fazer vocês, ou nós, reescrevermos tudo em Rust. Não eles próprios
    • Pessoas da área de linguagens de programação também gostam de bootstrapping. Escrever Rust em Rust não é algo tão absurdo assim
  • É 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

    • Talvez você se surpreenda ao saber que há muitos recursos propostos por pessoas aleatórias que acabam sendo rejeitados pela comunidade Rust
      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
    • Sou o autor do texto original. Acho que o orçamento de complexidade do Rust está sendo gasto em muitos lugares errados
      Por exemplo, a interação entre Pin e futures adiciona uma complexidade absurda à linguagem, e acho que parte dela é desnecessária. Eu gostaria que existisse uma linguagem parecida com Rust sem nenhum Pin. 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 maior
    • Mais financiamento pode atrair o tipo errado de pessoas. A ruindade delas e o potencial de causar danos às vezes só ficam evidentes tarde demais
    • Fico curioso sobre qual drama da comunidade Rust você está falando
      Vejo 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 cemitério de recursos enterrados no nightly é, de fato, bem grande. Recursos importantes como especialização também estão stuck ali para sempre
  • 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 Mutex e RwLock do Rust para que usassem diretamente os recursos primitivos do SO subjacente em cada sistema operacional principal
    Usando coisas como futex no 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 Rust
    https://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 AsyncWrite e AsyncRead; 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, como AsyncIterator, 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 demais
    Traits 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, try e const. 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 correto
    Structs 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 fortalecer macro_rules foi escrita recentemente. O impl de Range já está em andamento, atrelado a mudanças incompatíveis por meio de uma edição. A combinação de if let com 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ção

    • É assim que se deve desenvolver pacotes fundamentais no nível da biblioteca padrão. As linguagens usadas hoje continuarão sendo usadas por muitas décadas, e tomar agora decisões lentas, mas boas economizará muito mais tempo no futuro
    • Houve muita discussão sobre sandboxing de proc macros e scripts de build
      Macros mais declarativas, delegação da lógica de crates -sys para bibliotecas compartilhadas e cfg(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 que cargo-redpen também está evoluindo como ferramenta de auditoria de chamadas, mas imagino algo de nível mais alto, como o cackle
      https://github.com/cackle-rs/cackle
    • Sou o autor do post original. É bom poder ouvir uma perspectiva interna em detalhes
      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 unsafe de 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 reduzido
  • A 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/

    • Não quero diminuir o QBE, mas o objetivo inicial do QBE era entregar 90% do desempenho com 10% do código, e depois isso mudou para 70%
      Seja com Rust ou qualquer outra coisa, é difícil saber o quanto isso é possível antes de realmente tentar