IT정보

Rust 1.82.0 발표 (번역)

break; 2024. 11. 12. 23:03
반응형

Rust 팀은 Rust의 새로운 버전인 1.82.0을 발표하게 되어 기쁩니다. Rust는 모든 사람이 안정적이고 효율적인 소프트웨어를 구축할 수 있도록 하는 프로그래밍 언어입니다.

다음을 통해 이전 버전의 Rust가 설치되어 있다면 rustup다음을 통해 1.82.0을 받을 수 있습니다.

$ rustup update stable

아직 설치하지 않으셨다면 당사 웹사이트의 해당 페이지에서 다운로드rustup 할 수 있으며, 1.82.0에 대한 자세한 릴리스 노트를 확인하실 수 있습니다 .

향후 릴리스를 테스트하여 저희를 도와주고 싶으시다면 로컬로 업데이트하여 베타 채널( rustup default beta) 또는 야간 채널( rustup default nightly)을 사용하는 것을 고려해 보세요. 발견한 버그가 있으면 알려 주세요!

1.82.0 안정판에 무엇이 들어있나요?

cargo info

Cargo에는 이제 레지스트리에 있는 패키지에 대한 정보를 표시하는 info하위 명령이 있어 10주년이 채 되지 않은 오랜 요청을 충족합니다 ! 이와 같은 여러 타사 확장 기능이 수년에 걸쳐 작성되었으며, 이 구현은 Cargo 자체에 병합되기 전에 cargo-information 으로 개발되었습니다 .

예를 들어, 다음과 같은 내용을 확인할 수 있습니다 cargo info cc.

cc #build-dependencies
A build-time dependency for Cargo build scripts to assist in invoking the native
C compiler to compile native C code into a static archive to be linked into Rust
code.
version: 1.1.23 (latest 1.1.30)
license: MIT OR Apache-2.0
rust-version: 1.63
documentation: https://docs.rs/cc
homepage: https://github.com/rust-lang/cc-rs
repository: https://github.com/rust-lang/cc-rs
crates.io: https://crates.io/crates/cc/1.1.23
features:
  jobserver = []
  parallel  = [dep:libc, dep:jobserver]
note: to see how you depend on cc, run `cargo tree --invert --package cc@1.1.23`

기본적으로 cargo info로컬에 있는 패키지 버전을 설명합니다 Cargo.lock(있는 경우). 보시다시피, 새 버전이 있을 때도 표시하고 cargo info cc@1.1.30보고합니다.

애플 타겟 프로모션

64비트 ARM 기반 macOS는 이제 Tier 1입니다.

aarch64-apple-darwin64비트 ARM(M1-family 또는 이후 Apple Silicon CPU)에서 macOS용 Rust 타겟은 이제 1계층 타겟이 되었으며, 이는 제대로 작동할 수 있는 가장 높은 보장을 나타냅니다. 플랫폼 지원 페이지에 설명된 대로 Rust 저장소의 모든 변경 사항은 병합되기 전에 모든 1계층 타겟에서 전체 테스트를 통과해야 합니다. 이 타겟은 Rust 1.49에서 2계층으로 도입되어 .에서 사용할 수 있게 되었습니다 . 이 새로운 이정표는 타겟을 64비트 ARM Linux 및 X86 macOS, Linux, Windows 타겟과 동등한 수준으로 rustup만듭니다 .aarch64-apple-darwin

Mac Catalyst 타겟은 이제 Tier 2입니다.

Mac Catalyst 는 Apple에서 제공하는 기술로, Mac에서 iOS 애플리케이션을 네이티브로 실행할 수 있게 해줍니다. 이는 iOS 전용 코드를 테스트할 때 특히 유용한데, cargo test --target=aarch64-apple-ios-macabi --target=x86_64-apple-ios-macabi대부분 그냥 작동하기 때문입니다(네이티브 기기나 시뮬레이터에서 실행하기 전에 외부 툴링을 사용하여 번들링해야 하는 일반적인 iOS 대상과 대조적으로).

대상은 이제 Tier 2이며, 로 다운로드할 수 있습니다 rustup target add aarch64-apple-ios-macabi x86_64-apple-ios-macabi. 따라서 CI 파이프라인을 업데이트하여 코드가 iOS와 유사한 환경에서도 실행되는지 테스트하기에 아주 좋은 시기입니다.

정확한 캡처 use<..>링 구문

Rust는 이제 use<..>특정 impl Trait범위 내에서 구문을 지원하여 어떤 일반적인 수명 매개변수를 캡처할지 제어합니다.

Rust의 Return-position impl Trait(RPIT) 유형은 특정 일반 매개변수를 캡처합니다 . 일반 매개변수를 캡처하면 해당 매개변수를 숨겨진 유형에서 사용할 수 있습니다. 이는 다시 대출 검사에 영향을 미칩니다.

Rust 2021 및 이전 버전에서 수명 매개변수는 불투명한 유형에서 베어 함수와 내재적 구현의 함수 및 메서드에서 캡처되지 않습니다. 단, 해당 수명 매개변수가 불투명한 유형에서 구문적으로 언급되지 않는 한 그렇습니다. 예를 들어, 이는 오류입니다.

//@ edition: 2021
fn f(x: &()) -> impl Sized { x }
error[E0700]: hidden type for `impl Sized` captures lifetime that does not appear in bounds
 --> src/main.rs:1:30
  |
1 | fn f(x: &()) -> impl Sized { x }
  |         ---     ----------   ^
  |         |       |
  |         |       opaque type defined here
  |         hidden type `&()` captures the anonymous lifetime defined here
  |
help: add a `use<...>` bound to explicitly capture `'_`
  |
1 | fn f(x: &()) -> impl Sized + use<'_> { x }
  |                            +++++++++

새로운 use<..>구문을 사용하면 오류에서 제안한 대로 다음과 같이 작성하여 이 문제를 해결할 수 있습니다.

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

이전에는 이러한 종류의 오류를 올바르게 수정하려면 기존에 라고 불리는 더미 특성을 정의 Captures하고 다음과 같이 사용해야 했습니다.

trait Captures<T: ?Sized> {}
impl<T: ?Sized, U: ?Sized> Captures<T> for U {}

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

그것을 " Captures트릭" 이라고 불렀고 , 약간 바로크하고 미묘했습니다. 더 이상 필요하지 않습니다.

이것을 고치는 덜 정확하지만 더 편리한 방법이 있었는데, 종종 "outlives trick" 이라고 불렸습니다 . 컴파일러는 이전에도 이렇게 하라고 제안했습니다. 그 트릭은 다음과 같았습니다.

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

이 간단한 사례에서 이 트릭은 RFC 3498+ use<'_> 에서 설명한 미묘한 이유 와 정확히 동일합니다 . 그러나 실제 사례에서 이는 반환된 불투명한 유형의 경계를 과도하게 제한하여 문제를 일으킵니다. 예를 들어, Rust 컴파일러의 실제 사례에서 영감을 받은 이 코드를 고려해 보세요.

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

fn f<'cx, 'a>(
    cx: Ctx<'cx>,
    x: &'a u8,
) -> impl Iterator<Item = &'a u8> + 'cx {
    core::iter::once_with(move || {
        eprintln!("LOG: {}", cx.0);
        x
    })
//~^ ERROR lifetime may not live long enough
}

+ 'cx우리는 , 수명이 숨겨진 유형에서 사용되므로 캡처되어야 하므로 제거할 수 없습니다 . 'a: 'cx또한 , 수명이 실제로 관련이 없고 일반적으로 , 보다 'a오래 지속 되는 것은 사실이 아니므로 , 의 경계를 추가할 수도 없습니다 'cx. 그러나 대신 작성하면 + use<'cx, 'a>작동하고 올바른 경계를 갖게 됩니다.

오늘 안정화하는 것에는 몇 가지 제한이 있습니다. use<..>구문은 현재 특성 또는 특성 구현 내에 나타날 수 없습니다(하지만 범위 내 수명 매개변수는 기본적으로 이미 캡처됨). 그리고 모든 범위 내 일반 유형 및 const 매개변수를 나열해야 합니다. 시간이 지나면서 이러한 제한을 해제할 수 있기를 바랍니다.

Rust 2024에서 위의 예는 use<..>구문(또는 어떤 트릭)이 필요 없이 "그냥 작동"합니다. 이는 새로운 버전에서 불투명한 유형이 범위 내의 모든 수명 매개변수를 자동으로 캡처하기 때문입니다. 이는 더 나은 기본값이며, 이것이 코드를 정리하는 방법에 대한 많은 증거를 보았습니다. Rust 2024에서 use<..>구문은 이 기본값을 옵트아웃하는 중요한 방법으로 사용될 것입니다.

use<..>구문, 캡처링 및 이것이 Rust 2024에 적용되는 방식 에 대한 자세한 내용은 에디션 가이드의 "RPIT lifetime capture rules" 장을 참조하세요. 전반적인 방향에 대한 자세한 내용은 최근 블로그 게시물 "Changes to impl Traitin Rust 2024" 를 참조하세요 .

원시 포인터를 생성하기 위한 기본 구문

안전하지 않은 코드는 때때로 매달리거나, 정렬이 잘못되었거나, 유효한 데이터를 가리키지 않는 포인터를 처리해야 합니다. 이런 일이 자주 일어나는 일반적인 사례는 repr(packed)구조체입니다. 이런 경우 참조를 만들지 않는 것이 중요합니다. 참조를 만들면 정의되지 않은 동작이 발생하기 때문입니다. 즉, 일반적인 &and &mut연산자는 참조를 만들기 때문에 사용할 수 없습니다. 참조가 즉시 원시 포인터로 캐스팅되더라도 정의되지 않은 동작을 피하기에는 너무 늦습니다.

수년 동안 매크로 std::ptr::addr_of!와 는 std::ptr::addr_of_mut!이 목적을 위해 사용되었습니다. 이제 이 작업에 대한 적절한 네이티브 구문을 제공할 때가 되었습니다. , addr_of!(expr)는 &raw const expr, addr_of_mut!(expr)는 &raw mut expr. 예를 들어:

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

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

    // This would be undefined behavior!
    // It is rejected by the compiler.
    //let ptr = &p.not_aligned_field as *const i32;

    // This is the old way of creating a pointer.
    let ptr = std::ptr::addr_of!(p.not_aligned_field);

    // This is the new way.
    let ptr = &raw const p.not_aligned_field;

    // Accessing the pointer has not changed.
    // Note that `val = *ptr` would be undefined behavior because
    // the pointer is not aligned!
    let val = unsafe { ptr.read_unaligned() };
}

네이티브 구문은 이러한 연산자의 피연산자 표현식이 장소 표현식 으로 해석된다는 것을 더 명확하게 합니다 . 또한 포인터를 만드는 동작을 언급할 때 "address-of"라는 용어를 피합니다. 포인터는 주소 그 이상 이므로 Rust는 포인터와 주소의 거짓 동등성을 재확인하는 "address-of"와 같은 용어에서 벗어나고 있습니다.

안전한 품목unsafe extern

Rust 코드는 외래 코드의 함수와 정적 변수를 사용할 수 있습니다. 이러한 외래 항목의 형식 서명은 extern블록에 제공됩니다. 역사적으로 블록 내의 모든 항목은 사용하기에 안전하지 않았지만, 블록 자체 에는 어디에도 extern쓸 필요가 없었습니다 .unsafeextern

하지만 블록 내의 서명이 extern올바르지 않다면, 그 아이템을 사용하면 정의되지 않은 동작이 발생합니다. 그것은 블록을 쓴 사람의 잘못일까요 extern, 아니면 그 아이템을 사용한 사람의 잘못일까요?

extern우리는 블록을 작성한 사람이 블록에 포함된 모든 서명이 정확한지 확인하는 것이 책임이라고 결정했고 , 이제 다음과 같이 작성할 수 있도록 허용합니다 unsafe extern.

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

이것의 한 가지 이점은 unsafe extern블록 내의 항목을 사용하기에 안전하다고 표시할 수 있다는 것입니다. 위의 예에서 . 을 사용하지 않고도 call sqrt또는 read를 할 수 있습니다. 또는 로 표시되지 않은 항목은 보수적으로 .으로 가정합니다 .TAUunsafesafeunsafeunsafe

향후 릴리스에서는 with lints 사용을 장려할 것입니다 unsafe extern. Rust 2024부터 using 이 unsafe extern필수가 될 것입니다.

자세한 내용은 RFC 3484 및 편집 가이드의 "안전하지 않은 extern 블록" 장을 참조하세요.

안전하지 않은 속성

와 같은 일부 Rust 속성은 블록 없이 정의되지 않은 동작을 유발하는no_mangle 데 사용될 수 있습니다 . 이것이 일반 코드라면 블록에 배치해야 하지만 지금까지 속성은 비슷한 구문을 가지고 있지 않았습니다. 이러한 속성이 Rust의 안전성 보장을 훼손할 수 있다는 사실을 반영하기 위해 이제 "안전하지 않음"으로 간주되며 다음과 같이 작성해야 합니다.unsafeunsafe {}

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

현재 이 속성의 이전 형식(없는 형식 unsafe)은 여전히 ​​허용되지만, 미래의 어느 시점에서 린트 처리될 수 있으며, Rust 2024에서는 심각한 오류가 될 것입니다.

이는 다음 속성에 영향을 미칩니다.

  • no_mangle
  • link_section
  • export_name

자세한 내용은 에디션 가이드의 "안전하지 않은 속성" 장을 참조하세요.

패턴 매칭에서 빈 유형 생략

값으로 비어 있는(즉, 거주하지 않는) 유형과 일치하는 패턴은 이제 생략할 수 있습니다.

use std::convert::Infallible;
pub fn unwrap_without_panic<T>(x: Result<T, Infallible>) -> T {
    let Ok(x) = x; // the `Err` case does not need to appear
    x
}

이것은 variant-less 와 같은 빈 유형 enum Void {}이나, 보이는 빈 필드와 #[non_exhaustive]속성이 없는 구조체와 열거형에서 작동합니다. 또한 never 유형과 함께 사용하면 특히 유용할 것입니다 !. 하지만 이 유형은 현재로선 불안정합니다.

빈 패턴을 여전히 작성해야 하는 경우가 있습니다. 초기화되지 않은 값과 안전하지 않은 코드와 관련된 이유로, 빈 형식이 참조, 포인터 또는 유니언 필드를 통해 액세스되는 경우 패턴을 생략하는 것은 허용되지 않습니다.

pub fn unwrap_ref_without_panic<T>(x: &Result<T, Infallible>) -> &T {
    match x {
        Ok(x) => x,
        // this arm cannot be omitted because of the reference
        Err(infallible) => match *infallible {},
    }
}

여러 Rust 버전을 지원하려는 상자를 방해하지 않기 위해, match빈 패턴이 있는 arm은 제거할 수 있다는 사실에도 불구하고 아직 "도달할 수 없는 코드" 경고로 보고되지 않습니다.

부동 소수점 NaN 의미론 및const

부동 소수점 값( f32및 유형 f64)에 대한 연산은 유명하게도 미묘합니다. 그 이유 중 하나는 예를 들어 의 결과를 나타내는 데 사용되는 NaN("숫자가 아님") 값이 존재하기 때문입니다 0.0 / 0.0. NaN 값을 미묘하게 만드는 것은 두 개 이상의 가능한 NaN 값이 존재한다는 것입니다. NaN 값에는 부호( 로 확인할 수 있음 f.is_sign_positive())와 페이로드( 로 추출할 수 있음 f.to_bits())가 있습니다. 그러나 NaN 값의 부호와 페이로드는 모두 ==( 항상 를 반환함 false)에서 완전히 무시됩니다. 하드웨어 아키텍처에서 부동 소수점 연산의 동작을 표준화하려는 매우 성공적인 노력에도 불구하고 NaN이 양수 또는 음수인 경우와 정확한 페이로드에 대한 세부 정보는 아키텍처마다 다릅니다. 문제를 더욱 복잡하게 만들기 위해 Rust와 LLVM 백엔드는 정확한 숫자 결과가 변경되지 않는 것이 보장되는 경우 부동 소수점 연산에 최적화를 적용하지만 이러한 최적화는 생성되는 NaN 값을 변경할 수 있습니다. 예를 들어 는 f * 1.0로만 최적화될 수 있습니다 f. 그러나 가 NaN인 경우 f결과의 정확한 비트 패턴이 변경될 수 있습니다!

이 릴리스에서 Rust는 NaN 값이 동작하는 방식에 대한 규칙 집합을 표준화합니다. 이 규칙 집합은 완전히 결정적이지 않으므로 와 같은 연산의 결과는 (0.0 / 0.0).is_sign_positive()하드웨어 아키텍처, 최적화 수준 및 주변 코드에 따라 달라질 수 있습니다. 완전히 이식 가능한 코드를 목표로 하는 코드는 사용을 피해야 하며 대신 to_bits를 사용해야 합니다 . 그러나 규칙은 NaN 박싱과 같은 고급 데이터 표현 기술을 Rust 코드에 구현할 수 있도록 신중하게 선택되었습니다. 정확한 규칙에 대한 자세한 내용은 설명서를 확인하세요 .f.signum() == 1.0f.is_sign_positive()

NaN 값에 대한 의미론이 정해지면서 이 릴리스에서는 .에서 부동 소수점 연산을 사용할 수도 있습니다 const fn. 위에서 설명한 이유로 인해 (0.0 / 0.0).is_sign_positive()(Rust 1.83에서 const-stable이 될)과 같은 연산은 컴파일 타임과 런타임에 실행될 때 다른 결과를 생성할 수 있습니다. 이는 버그가 아니며 코드는 const fn항상 정확히 동일한 결과를 생성하는 것에 의존해서는 안 됩니다.

어셈블리 직접 변수로서의 상수

어셈블리 const피연산자는 이제 레지스터에 먼저 저장하지 않고도 정수를 즉시 사용할 수 있는 방법을 제공합니다. 예를 들어, 우리는 write수동으로 syscall을 구현합니다.

const WRITE_SYSCALL: c_int = 0x01; // syscall 1 is `write`
const STDOUT_HANDLE: c_int = 0x01; // `stdout` has file handle 1
const MSG: &str = "Hello, world!\n";

let written: usize;

// Signature: `ssize_t write(int fd, const void buf[], size_t count)`
unsafe {
    core::arch::asm!(
        "mov rax, {SYSCALL} // rax holds the syscall number",
        "mov rdi, {OUTPUT}  // rdi is `fd` (first argument)",
        "mov rdx, {LEN}     // rdx is `count` (third argument)",
        "syscall            // invoke the syscall",
        "mov {written}, rax // save the return value",
        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());

산출:

Hello, world!

놀이터 링크 .

위에서와 같이, 와 같은 문장은 LEN = const MSG.len()형식 지정자를 LEN값을 취하는 즉시로 채웁니다 MSG.len(). 이것은 생성된 어셈블리에서 볼 수 있습니다(값은 14):

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

자세한 내용은 참고문헌을 확인하세요 .

static안전하지 않은 문제를 안전하게 해결

이제 이 코드가 허용됩니다:

static mut STATIC_MUT: Type = Type::new();
extern "C" {
    static EXTERN_STATIC: Type;
}
fn main() {
     let static_mut_ptr = &raw mut STATIC_MUT;
     let extern_static_ptr = &raw const EXTERN_STATIC;
}

표현식 컨텍스트에서 STATIC_MUT및 는 위치 표현식EXTERN_STATIC 입니다 . 이전에 컴파일러의 안전 검사는 원시 참조 연산자가 피연산자의 위치에 실제로 영향을 미치지 않고 포인터에 대한 읽기 또는 쓰기로 처리한다는 것을 인식하지 못했습니다. 그러나 포인터를 생성할 뿐이므로 실제로 안전하지 않은 것은 없습니다.

이것을 완화하면 lint를 거부하면 일부 안전하지 않은 블록이 사용되지 않는 것으로 보고되는 문제가 발생할 수 있지만 unused_unsafe, 이제 이전 버전에서만 유용합니다. #[allow(unused_unsafe)]이 예제 diff에서와 같이 여러 버전의 Rust를 지원하려면 이러한 안전하지 않은 블록에 주석을 달면 됩니다.

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

Rust의 향후 버전에서는 이를 정적 표현식뿐 아니라 이 위치에서 안전한 다른 표현식으로 일반화할 것으로 기대됩니다.

안정화된 API

이제 다음 API는 const 컨텍스트에서 안정적입니다.

기타 변경 사항

Rust , Cargo , Clippy 에서 변경된 모든 내용을 확인하세요 .

1.82.0에 기여한 사람들

많은 사람들이 Rust 1.82.0을 만들기 위해 함께 모였습니다. 여러분 모두 없었다면 우리는 할 수 없었을 것입니다. 감사합니다!

 

출처: https://blog.rust-lang.org/2024/10/17/Rust-1.82.0.html
반응형