Rust 1.27 Release

A equipe de desenvolvimento do Rust tem o prazer de anunciar o lançamento de uma nova versão do Rust: 1.27.0. Rust é uma linguagem de programação de sistema destinada a segurança, velocidade e execução de código paralelo.


Se você possui uma versão anterior do Rust instalada usando rustup, para fazer o upgrade do Rust para a versão 1.27.0, basta fazer o seguinte:


$ rustup update stable 

Se você ainda não instalou o rustup, poderá instalá-lo na página correspondente do nosso site. Notas de versão detalhadas do Rust 1.27.0 estão disponíveis no GitHub.


Também queremos chamar sua atenção para isso: antes do lançamento da versão 1.27.0, descobrimos um erro ao melhorar match mapeamentos de match introduzidos na versão 1.26.0, o que poderia levar a um comportamento incorreto. Como foi descoberta muito tarde, já em processo de lançamento desta versão, embora esteja presente desde a versão 1.26.0, decidimos não interromper a rotina e preparar uma versão fixa 1.27.1, que será lançada em um futuro próximo. Além disso, se necessário, a versão 1.26.3. Detalhes podem ser encontrados nas notas de versão correspondentes.


O que está incluído na versão estável 1.27.0


Nesta edição, duas grandes e esperadas melhorias de idioma são lançadas. Mas primeiro, um pequeno comentário sobre a documentação: a pesquisa agora está disponível em todos os livros da biblioteca Rust ! Por exemplo, você pode encontrar "emprestar" no livro "Linguagem de programação de ferrugem" . Esperamos que isso facilite a localização das informações necessárias. Além disso, um novo livro sobre rustc apareceu . Este livro explica como usar o rustc diretamente e como obter outras informações úteis, como uma lista de todas as verificações estáticas.


SIMD


Portanto, agora o importante: a partir de agora, os recursos básicos do uso do SIMD estão disponíveis no Rust! SIMD significa "instrução única, fluxo de dados múltiplos" (instrução única, dados múltiplos). Considere a função:


 pub fn foo(a: &[u8], b: &[u8], c: &mut [u8]) { for ((a, b), c) in a.iter().zip(b).zip(c) { *c = *a + *b; } } 

Aqui pegamos duas fatias inteiras, somamos seus elementos e colocamos o resultado na terceira fatia. O código acima demonstra a maneira mais fácil de fazer isso: você precisa percorrer todo o conjunto de elementos, reuni-los e salvar o resultado. No entanto, os compiladores geralmente encontram uma solução melhor. O LLVM geralmente "vetoriza automaticamente" código semelhante, onde uma formulação tão complexa significa simplesmente "usa SIMD". Imagine que as fatias b têm 16 elementos, ambos. Cada elemento é u8 , o que significa que as fatias conterão 128 bits de dados cada. Usando o SIMD, podemos colocar as fatias b nos registros de 128 bits, adicioná-las com uma instrução e copiar os 128 bits resultantes para c . Funcionará muito mais rápido!


Apesar do fato de que a versão estável do Rust sempre conseguiu tirar proveito da vetorização automática, algumas vezes o compilador simplesmente não é inteligente o suficiente para entender que pode ser aplicado nesse caso. Além disso, nem todas as CPUs suportam esses recursos. Portanto, o LLVM nem sempre pode usá-los, pois seu programa pode ser executado em uma variedade de plataformas de hardware. Portanto, no Rust 1.27, com a adição do módulo std::arch , tornou-se possível usar esses tipos de instruções diretamente , ou seja, agora não somos obrigados a confiar apenas na compilação inteligente. Além disso, temos a oportunidade de escolher uma implementação específica, dependendo de vários critérios. Por exemplo:


 #[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), target_feature = "avx2"))] fn foo() { #[cfg(target_arch = "x86")] use std::arch::x86::_mm256_add_epi64; #[cfg(target_arch = "x86_64")] use std::arch::x86_64::_mm256_add_epi64; unsafe { _mm256_add_epi64(...); } } 

Aqui, usamos os sinalizadores cfg para selecionar a versão correta do código, dependendo da plataforma de destino: no x86 sua própria versão será usada e no x86_64 . Também podemos escolher em tempo de execução:


 fn foo() { #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { if is_x86_feature_detected!("avx2") { return unsafe { foo_avx2() }; } } foo_fallback(); } 

Aqui temos duas versões da função: uma utiliza o AVX2 - um tipo específico de SIMD que permite executar operações de 256 bits. Macro is_x86_feature_detected! irá gerar um código que verifica se o processador suporta o AVX2 e, nesse caso, a função foo_avx2 será chamada. Caso contrário, recorreremos a uma implementação sem o AVX, foo_fallback . Portanto, nosso código funcionará muito rápido em processadores compatíveis com o AVX2, mas também funcionará em outros processadores, embora mais devagar.


Tudo parece um pouco baixo e desconfortável - sim, é! std::arch são os primitivos para esse tipo de coisa. Esperamos que, no futuro, ainda estabilizemos o std::simd com recursos de alto nível. Mas o surgimento dos recursos básicos do SIMD agora permite que você experimente o suporte de alto nível para várias bibliotecas. Por exemplo, confira o pacote mais rápido . Aqui está um trecho de código sem SIMD:


 let lots_of_3s = (&[-123.456f32; 128][..]).iter() .map(|v| { 9.0 * v.abs().sqrt().sqrt().recip().ceil().sqrt() - 4.0 - 2.0 }) .collect::<Vec<f32>>(); 

Para usar o SIMD neste código com faster , é necessário alterá-lo da seguinte maneira:


 let lots_of_3s = (&[-123.456f32; 128][..]).simd_iter() .simd_map(f32s(0.0), |v| { f32s(9.0) * v.abs().sqrt().rsqrt().ceil().sqrt() - f32s(4.0) - f32s(2.0) }) .scalar_collect(); 

Parece quase o mesmo: simd_iter vez de iter , simd_map vez de map , f32s(2.0) vez de 2.0 . Mas, no final, você obtém uma versão do seu código certificada pelo SIMD.


Além disso , você nunca pode escrever isso sozinho, mas, como sempre, as bibliotecas de que você depende podem fazer isso. Por exemplo, o suporte já foi adicionado regex e sua nova versão terá aceleração SIMD sem a necessidade de você fazer qualquer coisa!


dyn Trait


No final, lamentamos a sintaxe inicialmente selecionada dos objetos de característica no Rust. Como você se lembra, para uma Foo você pode definir um objeto de característica como este:


 Box<Foo> 

No entanto, se Foo fosse uma estrutura, isso significaria simplesmente colocar a estrutura dentro de uma Box<T> . Ao desenvolver a linguagem, pensamos que essas semelhanças seriam uma boa idéia, mas a experiência mostrou que isso leva à confusão. E não é apenas Box<Trait> : impl SomeTrait for SomeOtherTrait também impl SomeTrait for SomeOtherTrait uma sintaxe formalmente correta, mas você quase sempre precisa escrever impl<T> SomeTrait for T where T: SomeOtherTrait . É o mesmo com o impl SomeTrait , que parece adicionar métodos ou uma possível implementação padrão ao tipo, mas, na verdade, adiciona seus próprios métodos ao objeto de tipo. Finalmente, em comparação com a sintaxe impl Trait recentemente adicionada, a sintaxe Trait parece mais curta e preferível de usar, mas, na realidade, isso nem sempre é verdade.


Portanto, no Rust 1.27, estabilizamos a nova sintaxe dyn Trait . Os objetos Trait agora têm esta aparência:


 //  =>  Box<Foo> => Box<dyn Foo> &Foo => &dyn Foo &mut Foo => &mut dyn Foo 

Da mesma forma para outros tipos de ponteiros: Arc<Foo> agora Arc<Foo> Arc<dyn Foo> , etc. Devido ao requisito de compatibilidade com versões anteriores, não podemos remover a sintaxe antiga, mas adicionamos uma verificação estática de bare-trait-object , que por padrão resolve a sintaxe antiga. Se você quiser proibi-lo, poderá ativar esta verificação. Pensamos que, com a verificação ativada por padrão, agora muitos avisos serão exibidos.


A propósito, estamos trabalhando em uma ferramenta chamada rustfix , que pode atualizar automaticamente seu código para novos idiomas. Ele usará verificações estáticas semelhantes para isso. Fique atento aos anúncios rustfix em anúncios futuros.

#[must_use] para funções


Concluindo, o efeito do atributo #[must_use] foi expandido: agora ele pode ser usado para funções .


Anteriormente, aplicava-se apenas a tipos, como Result <T, E> . Mas agora você pode fazer isso:


 #[must_use] fn double(x: i32) -> i32 { 2 * x } fn main() { double(4); // warning: unused return value of `double` which must be used let _ = double(4); // (no warning) } 

Com esse atributo, também aprimoramos um pouco a biblioteca padrão : Clone::clone , Iterator::collect e ToOwned::to_owned emitem avisos se você não usar seus valores de retorno, o que ajudará a perceber operações caras cujos resultados você acidentalmente ignora.


Veja as notas de versão para mais detalhes.


Estabilização de bibliotecas


As seguintes novas APIs foram estabilizadas nesta versão:



Veja as notas de versão para mais detalhes.


Aprimoramentos de carga


A Cargo recebeu duas pequenas melhorias nesta versão. Primeiro, um --target-dir , que pode ser usado para alterar o diretório de execução do destino.


Além disso, a abordagem da Cargo sobre como lidar com as metas foi finalizada. O Cargo tenta detectar testes, exemplos e executáveis ​​no seu projeto. No entanto, às vezes é necessária uma configuração explícita. Mas na implementação inicial, isso foi problemático. Digamos que você tenha dois exemplos e o Cargo os detecte. Você deseja configurar um deles, para o qual você adiciona [[example]] ao Cargo.toml para especificar parâmetros de exemplo. Atualmente, o Cargo verá que você definiu o exemplo explicitamente e, portanto, não tentará detectar automaticamente outros. Isso é um pouco perturbador.


Portanto, 'auto'- Cargo.toml . Não podemos corrigir esse comportamento sem uma possível divisão de projetos que dependem dele inadvertidamente. Portanto, se você deseja configurar alguns objetivos, mas não todos, você pode definir a chave autoexamples como true na seção [package] .


Veja as notas de versão para mais detalhes.


Desenvolvedores 1.27.0


Muitas pessoas participaram do desenvolvimento do Rust 1.27. Não poderíamos ter concluído o trabalho sem cada um de vocês.


Obrigada


De um tradutor: expresso minha gratidão aos membros da comunidade ruRust e pessoalmente ozkriff por sua ajuda na tradução e revisão

Source: https://habr.com/ru/post/pt415091/


All Articles