El equipo de desarrollo de Rust se complace en anunciar el lanzamiento de una nueva versión de Rust: 1.27.0. Rust es un lenguaje de programación del sistema destinado a la seguridad, la velocidad y la ejecución de código paralelo.
Si tiene una versión anterior de Rust instalada usando Rustup, entonces para actualizar Rust a la versión 1.27.0 solo necesita hacer:
$ rustup update stable
Si aún no ha instalado Rustup, puede instalarlo desde la página correspondiente de nuestro sitio web. Las notas de lanzamiento detalladas para Rust 1.27.0 están disponibles en GitHub.
También queremos llamar su atención sobre esto: antes del lanzamiento de la versión 1.27.0, descubrimos un error en las asignaciones de match
introducidas en la versión 1.26.0, que podría conducir a un comportamiento incorrecto. Como se descubrió muy tarde, ya en el proceso de lanzamiento de esta versión, aunque ha estado presente desde la versión 1.26.0, decidimos no romper la rutina y preparar una versión fija 1.27.1, que se lanzará en un futuro próximo. Y adicionalmente, si es necesario, la versión 1.26.3. Los detalles se pueden encontrar en las notas de la versión correspondiente.
Lo que se incluye en la versión estable 1.27.0
En este número, salen dos mejoras de lenguaje grandes y muy esperadas. Pero primero, un pequeño comentario sobre la documentación: ¡la búsqueda ahora está disponible en todos los libros de la biblioteca Rust ! Por ejemplo, puede encontrar "pedir prestado" en el libro "Rust Programming Language" . Esperamos que esto facilite la búsqueda de la información que necesita. Además, ha aparecido un nuevo libro sobre rustc . Este libro explica cómo usar rustc
directamente y cómo obtener otra información útil, como una lista de todas las comprobaciones estáticas.
SIMD
Entonces, ahora sobre lo importante: a partir de ahora, las características básicas del uso de SIMD están disponibles en Rust. SIMD significa "instrucción única, flujo de datos múltiples" (instrucción única, datos múltiples). Considere la función:
pub fn foo(a: &[u8], b: &[u8], c: &mut [u8]) { for ((a, b), c) in a.iter().zip(b).zip(c) { *c = *a + *b; } }
Aquí tomamos dos segmentos enteros, sumamos sus elementos y colocamos el resultado en el tercer segmento. El código anterior muestra la forma más fácil de hacer esto: debe revisar todo el conjunto de elementos, unirlos y guardar el resultado. Sin embargo, los compiladores a menudo encuentran una mejor solución. LLVM a menudo "vectoriza automáticamente" un código similar, donde una redacción tan compleja simplemente significa "usa SIMD". Imagine que los cortes b
tienen 16 elementos de largo, ambos. Cada elemento es u8
, lo que significa que las rebanadas contendrán 128 bits de datos cada una. Con SIMD, podemos colocar ambos segmentos b
en registros de 128 bits, agregarlos junto con una instrucción y luego copiar los 128 bits resultantes a c
. ¡Funcionará mucho más rápido!
A pesar de que la versión estable de Rust siempre ha podido aprovechar la vectorización automática, a veces el compilador simplemente no es lo suficientemente inteligente como para comprender que se puede aplicar en este caso. Además, no todas las CPU admiten estas características. Por lo tanto, LLVM no siempre puede usarlos, ya que su programa puede ejecutarse en una variedad de plataformas de hardware. Por lo tanto, en Rust 1.27, con la adición del módulo std::arch
, se hizo posible usar este tipo de instrucciones directamente , es decir, ahora no estamos obligados a confiar solo en la compilación inteligente. Además, tenemos la oportunidad de elegir una implementación específica, dependiendo de varios criterios. Por ejemplo:
#[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(...); } }
Aquí usamos los indicadores de cfg
para seleccionar la versión correcta del código dependiendo de la plataforma de destino: en x86
usará su propia versión y en x86_64
. También podemos elegir en tiempo de ejecución:
fn foo() { #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { if is_x86_feature_detected!("avx2") { return unsafe { foo_avx2() }; } } foo_fallback(); }
Aquí tenemos dos versiones de la función: una usa AVX2
, un tipo específico de SIMD que le permite realizar operaciones de 256 bits. Macro is_x86_feature_detected!
generará un código que verifica si el procesador es compatible con AVX2 y, de ser así, se foo_avx2
la función foo_avx2
. Si no, recurriremos a una implementación sin AVX, foo_fallback
. Por lo tanto, nuestro código funcionará muy rápido en procesadores que admitan AVX2, pero también funcionará en otros procesadores, aunque más lentamente.
Todo parece un poco bajo e incómodo, ¡sí, lo es! std::arch
son las primitivas para este tipo de cosas. Esperamos que en el futuro std::simd
estabilizando el std::simd
con capacidades de alto nivel. Pero la aparición de capacidades básicas de SIMD ahora le permite experimentar con soporte de alto nivel para varias bibliotecas. Por ejemplo, mira el paquete más rápido . Aquí hay un fragmento de código sin 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 SIMD en este código con faster
, debe cambiarlo así:
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();
Se ve casi igual: simd_iter
lugar de iter
, simd_map
lugar de map
, f32s(2.0)
lugar de 2.0
. Pero al final, obtienes una versión certificada SIMD de tu código.
Además de esto , nunca puede escribir esto usted mismo, pero, como siempre, las bibliotecas de las que depende pueden hacerlo. Por ejemplo, ya se ha agregado soporte regex
, y su nueva versión tendrá aceleración SIMD sin la necesidad de que haga nada.
dyn Trait
Al final, lamentamos la sintaxis inicialmente seleccionada de los objetos de rasgo en Rust. Como recordará, para un Foo
puede definir un objeto de rasgo como este:
Box<Foo>
Sin embargo, si Foo
fuera una estructura, simplemente significaría colocar la estructura dentro de un Box<T>
. Al desarrollar el lenguaje, pensamos que tales similitudes serían una buena idea, pero la experiencia ha demostrado que esto conduce a la confusión. Y no es solo Box<Trait>
: impl SomeTrait for SomeOtherTrait
también impl SomeTrait for SomeOtherTrait
una sintaxis formalmente correcta, sino que casi siempre necesita escribir impl<T> SomeTrait for T where T: SomeOtherTrait
lugar. Es lo mismo con impl SomeTrait
, que parece que agrega métodos o una posible implementación predeterminada al tipo, pero de hecho agrega sus propios métodos al objeto de tipo. Finalmente, en comparación con la sintaxis impl Trait
recientemente agregada, la sintaxis de Trait
parece más corta y preferible de usar, pero en realidad esto no siempre es cierto.
Por lo tanto, en Rust 1.27, estabilizamos la nueva sintaxis dyn Trait
. Los objetos de rasgo ahora se ven así:
De manera similar para otros tipos de puntero: Arc<Foo>
ahora Arc<Foo>
Arc<dyn Foo>
, etc. Debido al requisito de compatibilidad con versiones anteriores, no podemos eliminar la sintaxis anterior, pero hemos agregado una comprobación estática de bare-trait-object
, que por defecto resuelve la sintaxis anterior. Si desea prohibirlo, puede activar esta verificación. Pensamos que con la verificación activada por defecto, ahora se mostrarán demasiadas advertencias.
Por cierto, estamos trabajando en una herramienta llamada rustfix
, que puede actualizar automáticamente su código a modismos más nuevos. Utilizará comprobaciones estáticas similares para esto. rustfix
atentos para los anuncios de rustfix
en futuros anuncios.
#[must_use]
para funciones
En conclusión, el efecto del atributo #[must_use]
ha expandido: ahora se puede usar para funciones .
Anteriormente, se aplicaba solo a tipos, como Result <T, E>
. Pero ahora puedes hacer esto:
#[must_use] fn double(x: i32) -> i32 { 2 * x } fn main() { double(4);
Con este atributo, también mejoramos ligeramente la biblioteca estándar : Clone::clone
, Iterator::collect
y ToOwned::to_owned
le dará advertencias si no utiliza sus valores de retorno, lo que lo ayudará a notar operaciones costosas cuyos resultados ignora accidentalmente.
Vea las notas de la versión para más detalles.
Estabilización de la biblioteca
Las siguientes API nuevas se han estabilizado en esta versión:
Vea las notas de la versión para más detalles.
Mejoras de carga
Cargo ha recibido dos mejoras menores en esta versión. En primer lugar, --target-dir
un --target-dir
, que se puede usar para cambiar el directorio de ejecución de destino.
Además, se ha finalizado el enfoque de Cargo sobre cómo manejar objetivos. Cargo intenta detectar pruebas, ejemplos y ejecutables dentro de su proyecto. Sin embargo, a veces se requiere una configuración explícita. Pero en la implementación inicial, esto fue problemático. Digamos que tiene dos ejemplos, y Cargo los detecta. Desea configurar uno de ellos, para lo cual agrega [[example]]
a Cargo.toml
para especificar parámetros de ejemplo. Actualmente, Cargo verá que ha definido el ejemplo explícitamente y, por lo tanto, no intentará detectar automáticamente otros. Esto es un poco molesto.
Por lo tanto, 'auto'- Cargo.toml
. No podemos solucionar este comportamiento sin un posible desglose de proyectos que dependen de él sin darse cuenta. Por lo tanto, si desea configurar algunos objetivos, pero no todos, puede establecer la clave autoexamples
en true
en la sección [package]
.
Vea las notas de la versión para más detalles.
Desarrolladores 1.27.0
Mucha gente participó en el desarrollo de Rust 1.27. No podríamos haber completado el trabajo sin cada uno de ustedes.
Gracias
De un traductor: expreso mi gratitud a los miembros de la comunidad ruRust y personalmente a ozkriff por su ayuda con la traducción y revisión