Home Ciencia y Tecnología Todos los detalles y cambios que vinieron con Rust 1.82.0

Todos los detalles y cambios que vinieron con Rust 1.82.0

39
0

El equipo de Rust se complace en anunciar una nueva versión de Rust, 1.82.0. Rust es un lenguaje de programación que permite a todos generar software program confiable y eficiente.

Si tiene una versión anterior de Rust instalada a través de rustuppuede obtener 1.82.0 con:

$ rustup replace steady

Si aún no lo tiene, puede obtener rustup Desde la página apropiada de nuestro sitio net y consulte las notas detalladas de la versión para 1.82.0.

Si desea ayudarnos probando lanzamientos futuros, podría considerar actualizarse localmente para usar el canal beta (rustup default beta) o el canal nocturno (rustup default nightly). ¡Informe cualquier error que pueda encontrar!

¿Qué hay en el establo 1.82.0?

cargo data

La carga ahora tiene un data Subcomando para mostrar información sobre un paquete en el registro, cumpliendo una solicitud de larga knowledge de su décimo aniversario. Se han escrito varias extensiones de terceros como esta a lo largo de los años, y esta implementación se desarrolló como información de carga antes de fusionarse en la carga misma.

Por ejemplo, esto es lo que podrías ver para cargo data cc:

cc #build-dependencies
A build-time dependency for Cargo construct scripts to help in invoking the native
C compiler to compile native C code right into a static archive to be linked into Rust
code.
model: 1.1.23 (newest 1.1.30)
license: MIT OR Apache-2.0
rust-version: 1.63
documentation: 
homepage: 
repository: 
crates.io: 
options:
  jobserver = []
  parallel  = [dep:libc, dep:jobserver]
observe: to see the way you depend upon cc, run `cargo tree --invert --package [email protected]`

Por defecto, cargo data describe la versión del paquete en el native Cargo.locksi lo hay. Como puede ver, indicará cuándo también hay una versión más nueva, y cargo data [email protected] informaría sobre eso.

Promociones objetivo de Apple

MacOS en el brazo de 64 bits ahora es Nivel 1

El objetivo de óxido aarch64-apple-darwin Para macOS en el brazo de 64 bits (CPU de silicio de manzana M1 de 64 bits o posterior) es ahora un objetivo de nivel 1, lo que indica nuestra mayor garantía de funcionar correctamente. Como la página de soporte de la plataforma describe, cada cambio en el repositorio de óxido debe pasar pruebas completas en cada objetivo de nivel 1 antes de que pueda fusionarse.

Este objetivo se introdujo como nivel 2 en Rust 1.49, lo que lo puso a disposición en rustup. Este nuevo hito pone el aarch64-apple-darwin Dirija a la par con el brazo de 64 bits Linux y los objetivos X86 MacOS, Linux y Home windows.

Los objetivos de Mac Catalyst ahora son Nivel 2

Mac Catalyst es una tecnología de Apple que permite ejecutar aplicaciones iOS de forma nativa en la Mac. Esto es especialmente útil al probar el código específico de iOS, como cargo check --target=aarch64-apple-ios-macabi --target=x86_64-apple-ios-macabi Principalmente solo funciona (en contraste con los objetivos habituales de iOS, que deben agruparse utilizando herramientas externas antes de que puedan ejecutarse en un dispositivo nativo o en el simulador).

Los objetivos ahora son de nivel 2 y se pueden descargar con rustup goal add aarch64-apple-ios-macabi x86_64-apple-ios-macabipor lo que ahora es un excelente momento para actualizar su tubería CI para probar que su código también se ejecuta en entornos similares a iOS.

Captura precisa use<..> sintaxis

El óxido ahora es appropriate use<..> Sintaxis dentro de cierto impl Trait Límites para controlar qué parámetros de vida genéricos se capturan.

Posición de retorno impl Trait (Rpit) tipos en óxido captura ciertos parámetros genéricos. Capturar un parámetro genérico permite que ese parámetro se use en el tipo oculto. Eso a su vez afecta la verificación de préstamos.

En las ediciones de Rust 2021 y anteriores, los parámetros de por vida no se capturan en tipos opacos en funciones desnudas y en funciones y métodos de implicaciones inherentes a menos que esos parámetros de por vida se mencionen sintácticamente en el tipo opaco. Por ejemplo, este es un error:

//@ version: 2021
fn f(x: &()) -> impl Sized { x }


error[E0700]: hidden sort for `impl Sized` captures lifetime that doesn't seem in bounds
 --> src/principal.rs:1:30
  |
1 | fn f(x: &()) -> impl Sized { x }
  |         ---     ----------   ^
  |         |       |
  |         |       opaque sort outlined right here
  |         hidden sort `&()` captures the nameless lifetime outlined right here
  |
assist: add a `use<...>` certain to explicitly seize `'_`
  |
1 | fn f(x: &()) -> impl Sized + use<'_> { x }
  |                            +++++++++

Con el nuevo use<..> Sintaxis, podemos solucionar esto, como se sugiere en el error, escribiendo:

fn f(x: &()) -> impl Sized + use<'_> { x }

Anteriormente, la fijación correcta de esta clase de error requería definir un rasgo ficticio, convencionalmente llamado Capturesy usarlo de la siguiente manera:

trait Captures {}
impl Captures for U {}

fn f(x: &()) -> impl Sized + Captures<&'_ ()> { x }

Que se llamaba “el Captures truco “, y period un poco barroco y sutil. Ya no es necesario.

Había una forma menos correcta pero más conveniente de solucionar esto que a menudo se usaba llamado “El truco de Outlives”. El compilador incluso sugirió previamente hacer esto. Ese truco se veía así:

fn f(x: &()) -> impl Sized + '_ { x }

En este easy caso, el truco es exactamente equivalente a + use<'_> Por razones sutiles explicadas en RFC 3498. Sin embargo, en casos de la vida actual, esto sobrecarga los límites del tipo opaco devuelto, lo que lleva a problemas. Por ejemplo, considere este código, que está inspirado en un caso actual en el compilador de óxido:

struct Ctx<'cx>(&'cx u8);

fn f<'cx, 'a>(
    cx: Ctx<'cx>,
    x: &'a u8,
) -> impl Iterator + 'cx {
    core::iter::once_with(transfer || {
        eprintln!("LOG: {}", cx.0);
        x
    })
//~^ ERROR lifetime could not reside lengthy sufficient
}

No podemos eliminar el + 'cxdado que la vida se usa en el tipo oculto y, por lo tanto, debe capturarse. Tampoco podemos agregar un límite de 'a: 'cxdado que estas vidas no están realmente relacionadas y en basic no será cierto que 'a vidas 'cx. Si escribimos + use<'cx, 'a> En cambio, sin embargo, esto funcionará y tendrá los límites correctos.

Hay algunas limitaciones para lo que estamos estabilizando hoy. El use<..> La sintaxis no puede aparecer actualmente dentro de los rasgos o dentro de los implicadores de rasgos (pero tenga en cuenta que allí, los parámetros de vida útil en el alcance ya están capturados de forma predeterminada), y debe enumerar todos los parámetros genéricos y constantes en el alcance. Esperamos levantar estas restricciones con el tiempo.

Tenga en cuenta que en Rust 2024, los ejemplos anteriores “solo funcionarán” sin necesidad use<..> Sintaxis (o cualquier truco). Esto se debe a que en la nueva edición, los tipos opacos capturarán automáticamente todos los parámetros de por vida en el alcance. Este es un mejor incumplimiento, y hemos visto mucha evidencia sobre cómo esto limpia el código. En Rust 2024, use<..> La sintaxis servirá como una forma importante de optar por ese incumplimiento.

Para más detalles sobre use<..> Sintaxis, captura y cómo se aplica a Rust 2024, consulte el capítulo “Reglas de captura de vida útil del RPIT” de la Guía de edición. Para obtener detalles sobre la dirección basic, consulte nuestra reciente publicación de weblog, “Cambios a impl Trait En Rust 2024 “.

Sintaxis nativa para crear un puntero sin procesar

El código inseguro a veces tiene que tratar con punteros que pueden colgar, pueden desalinearse o no apuntar a datos válidos. Un caso común en el que surge esto es repr(packed) estructuras. En tal caso, es importante evitar crear una referencia, ya que eso causaría un comportamiento indefinido. Esto significa lo recurring & y &mut Los operadores no se pueden usar, ya que los crean una referencia, incluso si la referencia se lanza inmediatamente a un puntero bruto, es demasiado tarde para evitar el comportamiento indefinido.

Durante varios años, las macros std::ptr::addr_of! y std::ptr::addr_of_mut! han servido este propósito. Ahora ha llegado el momento de proporcionar una sintaxis nativa adecuada para esta operación: addr_of!(expr) se convierte en &uncooked const expry addr_of_mut!(expr) se convierte en &uncooked mut expr. Por ejemplo:

#[repr(packed)]
struct Packed {
    not_aligned_field: i32,
}

fn principal() {
    let p = Packed { not_aligned_field: 1_82 };

    // This might be undefined habits!
    // It's rejected by the compiler.
    //let ptr = &p.not_aligned_field as *const i32;

    // That is the previous method of making a pointer.
    let ptr = std::ptr::addr_of!(p.not_aligned_field);

    // That is the brand new method.
    let ptr = &uncooked const p.not_aligned_field;

    // Accessing the pointer has not modified.
    // Observe that `val = *ptr` could be undefined habits as a result of
    // the pointer will not be aligned!
    let val = unsafe { ptr.read_unaligned() };
}

La sintaxis nativa deja más claro que la expresión de operando de estos operadores se interpreta como una expresión de lugar. También evita el término “dirección de” cuando se refiere a la acción de crear un puntero. Un puntero es más que una easy dirección, por lo que Rust se está alejando de términos como “dirección” que reafirma una falsa equivalencia de punteros y direcciones.

Artículos seguros con unsafe extern

El código de óxido puede usar funciones y estadísticas del código extranjero. Las firmas de tipo de estos artículos extranjeros se proporcionan en extern bloques. Históricamente, todos los artículos dentro extern Los bloques han sido inseguros de usar, pero no tuvimos que escribir unsafe en cualquier lugar del extern bloquearse.

Sin embargo, si una firma dentro del extern El bloque es incorrecto, entonces usar ese elemento dará como resultado un comportamiento indefinido. ¿Sería culpa de la persona que escribió el extern bloquear, o la persona que usó ese artículo?

Hemos decidido que es responsabilidad de la persona que escribe el extern bloque para garantizar que todas las firmas contenidas en él sean correctas, por lo que ahora permitimos escribir unsafe extern:

unsafe extern {
    pub protected static TAU: f64;
    pub protected fn sqrt(x: f64) -> f64;
    pub unsafe fn strlen(p: *const u8) -> usize;
}

Un beneficio de esto es que los artículos dentro de un unsafe extern El bloque puede marcarse como seguro de usar. En el ejemplo anterior, podemos llamar sqrt o leer TAU sin usar unsafe. Artículos que no están marcados con protected o unsafe se supone conservadoramente que son unsafe.

En futuras versiones, alentaremos el uso de unsafe extern con pelusas. Comenzando en Rust 2024, usando unsafe extern será requerido.

Para obtener más detalles, consulte RFC 3484 y el capítulo “Bloques externos inseguros” de la Guía de edición.

Atributos inseguros

Algunos atributos de óxido, como no_manglese puede usar para causar un comportamiento indefinido sin ningún unsafe bloquear. Si este fuera un código common, requeriríamos que se coloquen en un unsafe {} Bloque, pero hasta ahora los atributos no han tenido una sintaxis comparable. Para reflejar el hecho de que estos atributos pueden socavar las garantías de seguridad de Rust, ahora se consideran “inseguros” y deben escribirse de la siguiente manera:

#[unsafe(no_mangle)]
pub fn my_global_function() { }

La antigua forma del atributo (sin unsafe) Actualmente todavía se acepta, pero podría ser peleado en algún momento en el futuro, y será un error duro en Rust 2024.

Esto afecta los siguientes atributos:

  • no_mangle
  • link_section
  • export_name

Para obtener más detalles, consulte el capítulo “Atributos inseguros” de la Guía de edición.

Omitir tipos vacíos en la coincidencia de patrones

Los patrones que coinciden con los tipos vacíos (también conocidos como un inhabitado) por valor ahora se pueden omitir:

use std::convert::Infallible;
pub fn unwrap_without_panic(x: Consequence) -> T {
    let Okay(x) = x; // the `Err` case doesn't want to look
    x
}

Esto funciona con tipos vacíos como una variante sin variante enum Void {}o estructuras y enums con un campo vacío seen y no #[non_exhaustive] atributo. También será particularmente útil en combinación con el tipo nunca !aunque ese tipo todavía es inestable en este momento.

Hay algunos casos en los que aún se deben escribir patrones vacíos. Por razones relacionadas con valores no inicializados y código inseguro, no se permite omitir patrones si se accede al tipo vacío a través de una referencia, puntero o campo de la Unión:

pub fn unwrap_ref_without_panic(x: &Consequence) -> &T {
    match x {
        Okay(x) => x,
        // this arm can't be omitted due to the reference
        Err(infallible) => match *infallible {},
    }
}

Para evitar interferir con cajas que deseen apoyar varias versiones de óxido, match Los brazos con patrones vacíos aún no se informan como advertencias de “código inalcanzable”, a pesar del hecho de que se pueden eliminar.

Semántica nan de punto flotante y const

Operaciones en valores de punto flotante (de tipo f32 y f64) son famosos sutiles. Una de las razones de esto es la existencia de valores nan (“no un número”) que se utilizan para representar, por ejemplo, el resultado de 0.0 / 0.0. Lo que hace que los valores NAN sean sutiles es que existe más de un valor NAN posible. Un valor nan tiene un letrero (que se puede verificar con f.is_sign_positive()) y una carga útil (que se puede extraer con f.to_bits()).

Sin embargo, tanto el signo como la carga útil de los valores de NAN se ignoran por completo por == (que siempre regresa false). A pesar de los esfuerzos muy exitosos para estandarizar el comportamiento de las operaciones de punto flotante en las arquitecturas de {hardware}, los detalles de cuándo un nan es positivo o negativo y lo que su carga útil exacta es diferente entre las arquitecturas.

Para hacer las cosas aún más complicadas, Rust y su backend LLVM aplican optimizaciones a las operaciones de punto flotante cuando el resultado numérico exacto está garantizado de no cambiar, pero esas optimizaciones pueden cambiar qué valor NAN se produce. Por ejemplo, f * 1.0 puede ser optimizado para solo f. Sin embargo, si f es un nan, ¡esto puede cambiar el patrón de bits exacto del resultado!

Con esta versión, Rust estandariza en un conjunto de reglas sobre cómo se comportan los valores de NAN. Este conjunto de reglas es no completamente determinista, lo que significa que el resultado de operaciones como (0.0 / 0.0).is_sign_positive() puede diferir dependiendo de la arquitectura de {hardware}, los niveles de optimización y el código circundante. El código que tiene como objetivo ser completamente portátil debe evitar usar to_bits y debería usar f.signum() == 1.0 en lugar de f.is_sign_positive().

Sin embargo, las reglas se eligen cuidadosamente para permitir que las técnicas avanzadas de representación de datos, como el boxeo NAN se implementen en el código de óxido. Para obtener más detalles sobre cuáles son las reglas exactas, consulte nuestra documentación.

Con la semántica para los valores NAN establecidos, esta versión también permite el uso de operaciones de punto flotante en const fn. Debido a las razones descritas anteriormente, operaciones como (0.0 / 0.0).is_sign_positive() (que será constable en Rust 1.83) puede producir un resultado diferente cuando se ejecuta en el tiempo de compilación frente a tiempo de ejecución. Este no es un error, y el código no debe confiar en un const fn siempre produciendo exactamente el mismo resultado.

Constantes como ensamblaje inmediatamente

El const El operando de ensamblaje ahora proporciona una forma de usar enteros como inmediatos sin almacenarlos primero en un registro. Como ejemplo, implementamos un syscall para write a mano:

const WRITE_SYSCALL: c_int = 0x01; // syscall 1 is `write`
const STDOUT_HANDLE: c_int = 0x01; // `stdout` has file deal with 1
const MSG: &str = "Good day, world!n";

let written: usize;

// Signature: `ssize_t write(int fd, const void buf[], size_t rely)`
unsafe {
    core::arch::asm!(
        "mov rax, {SYSCALL} // rax holds the syscall quantity",
        "mov rdi, {OUTPUT}  // rdi is `fd` (first argument)",
        "mov rdx, {LEN}     // rdx is `rely` (third argument)",
        "syscall            // invoke the syscall",
        "mov {written}, rax // save the return worth",
        SYSCALL = const WRITE_SYSCALL,
        OUTPUT = const STDOUT_HANDLE,
        LEN = const MSG.len(),
        in("rsi") MSG.as_ptr(), // rsi is `buf *` (second argument)
        written = out(reg) written,
    );
}

assert_eq!(written, MSG.len());

Producción:

Good day, world!

Enlace del patio de recreo.

En lo anterior, una declaración como LEN = const MSG.len() Poblena el especificador de formato LEN con un inmediato que toma el valor de MSG.len(). Esto se puede ver en el ensamblaje generado (el valor es 14)

lea     rsi, [rip + .L__unnamed_3]
mov     rax, 1    # rax holds the syscall quantity
mov     rdi, 1    # rdi is `fd` (first argument)
mov     rdx, 14   # rdx is `rely` (third argument)
syscall # invoke the syscall
mov     rax, rax  # save the return worth

Vea la referencia para más detalles.

Abordando de forma segura insegura statics

Este código ahora está permitido:

static mut STATIC_MUT: Kind = Kind::new();
extern "C" {
    static EXTERN_STATIC: Kind;
}
fn principal() {
     let static_mut_ptr = &uncooked mut STATIC_MUT;
     let extern_static_ptr = &uncooked const EXTERN_STATIC;
}

En un contexto de expresión, STATIC_MUT y EXTERN_STATIC son expresiones de lugar. Anteriormente, las verificaciones de seguridad del compilador no sabían que el operador de REF RAW no afectaba el lugar del operando, tratándolo como una posible lectura o escritura en un puntero. Sin embargo, no hay seguridad en realidad presente, ya que solo crea un puntero.

Relajarse esto puede causar problemas en los que algunos bloques inseguros ahora se informan como no utilizados si niega el unused_unsafe Lint, pero ahora solo son útiles en versiones anteriores. Anotar estos bloques inseguros con #[allow(unused_unsafe)] Si desea admitir múltiples versiones de Rust, como en este ejemplo Diff:

 static mut STATIC_MUT: Kind = Kind::new();
 fn principal() {
+    #[allow(unused_unsafe)]
     let static_mut_ptr = unsafe { std::ptr::addr_of_mut!(STATIC_MUT) };
 }

Se espera que una versión futura de Rust generalice esto a otras expresiones que serían seguras en esta posición, no solo la estadística.

API estabilizadas

  • std::thread::Builder::spawn_unchecked
  • std::str::CharIndices::offset
  • std::possibility::Possibility::is_none_or
  • [T]::is_sorted
  • [T]::is_sorted_by
  • [T]::is_sorted_by_key
  • Iterator::is_sorted
  • Iterator::is_sorted_by
  • Iterator::is_sorted_by_key
  • std::future::Prepared::into_inner
  • std::iter::repeat_n
  • impl DoubleEndedIterator for Take>
  • impl ExactSizeIterator for Take>
  • impl ExactSizeIterator for Take>
  • impl Default for std::collections::binary_heap::Iter
  • impl Default for std::collections::btree_map::RangeMut
  • impl Default for std::collections::btree_map::ValuesMut
  • impl Default for std::collections::vec_deque::Iter
  • impl Default for std::collections::vec_deque::IterMut
  • Rc::new_uninit
  • Rc>::assume_init
  • Rc<[T]>::new_uninit_slice
  • Rc<[MaybeUninit]>::assume_init
  • Arc::new_uninit
  • Arc>::assume_init
  • Arc<[T]>::new_uninit_slice
  • Arc<[MaybeUninit]>::assume_init
  • Field::new_uninit
  • Field>::assume_init
  • Field<[T]>::new_uninit_slice
  • Field<[MaybeUninit]>::assume_init
  • core::arch::x86_64::_bextri_u64
  • core::arch::x86_64::_bextri_u32
  • core::arch::x86::_mm_broadcastsi128_si256
  • core::arch::x86::_mm256_stream_load_si256
  • core::arch::x86::_tzcnt_u16
  • core::arch::x86::_mm_extracti_si64
  • core::arch::x86::_mm_inserti_si64
  • core::arch::x86::_mm_storeu_si16
  • core::arch::x86::_mm_storeu_si32
  • core::arch::x86::_mm_storeu_si64
  • core::arch::x86::_mm_loadu_si16
  • core::arch::x86::_mm_loadu_si32
  • core::arch::wasm32::u8x16_relaxed_swizzle
  • core::arch::wasm32::i8x16_relaxed_swizzle
  • core::arch::wasm32::i32x4_relaxed_trunc_f32x4
  • core::arch::wasm32::u32x4_relaxed_trunc_f32x4
  • core::arch::wasm32::i32x4_relaxed_trunc_f64x2_zero
  • core::arch::wasm32::u32x4_relaxed_trunc_f64x2_zero
  • core::arch::wasm32::f32x4_relaxed_madd
  • core::arch::wasm32::f32x4_relaxed_nmadd
  • core::arch::wasm32::f64x2_relaxed_madd
  • core::arch::wasm32::f64x2_relaxed_nmadd
  • core::arch::wasm32::i8x16_relaxed_laneselect
  • core::arch::wasm32::u8x16_relaxed_laneselect
  • core::arch::wasm32::i16x8_relaxed_laneselect
  • core::arch::wasm32::u16x8_relaxed_laneselect
  • core::arch::wasm32::i32x4_relaxed_laneselect
  • core::arch::wasm32::u32x4_relaxed_laneselect
  • core::arch::wasm32::i64x2_relaxed_laneselect
  • core::arch::wasm32::u64x2_relaxed_laneselect
  • core::arch::wasm32::f32x4_relaxed_min
  • core::arch::wasm32::f32x4_relaxed_max
  • core::arch::wasm32::f64x2_relaxed_min
  • core::arch::wasm32::f64x2_relaxed_max
  • core::arch::wasm32::i16x8_relaxed_q15mulr
  • core::arch::wasm32::u16x8_relaxed_q15mulr
  • core::arch::wasm32::i16x8_relaxed_dot_i8x16_i7x16
  • core::arch::wasm32::u16x8_relaxed_dot_i8x16_i7x16
  • core::arch::wasm32::i32x4_relaxed_dot_i8x16_i7x16_add
  • core::arch::wasm32::u32x4_relaxed_dot_i8x16_i7x16_add

Estas API ahora son estables en contextos constantes:

  • std::activity::Waker::from_raw
  • std::activity::Context::from_waker
  • std::activity::Context::waker
  • $integer::from_str_radix
  • std::num::ParseIntError::variety

Otros cambios

Echa un vistazo a todo lo que cambió en óxido, carga y recortado.

Contribuyentes a 1.82.0

Muchas personas se unieron para crear Rust 1.82.0. No podríamos haberlo hecho sin todos ustedes. ¡Gracias!


El equipo de lanzamiento de Rust

También publicado aquí

Foto de Milad Fakurian en Unsplash

fuente

LEAVE A REPLY

Please enter your comment!
Please enter your name here