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:
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);
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