[Rust] 1.66.0 업데이트 발표 (번역)

https://blog.rust-lang.org/2022/12/15/Rust-1.66.0.html

우리 러스트 팀은 새 버전 [1.66.0]을 발표하게 돼서 정말 기쁩니다!
러스트는 누구든 믿음직하고 효과적인 소프트웨어를 만들 수 있게 도와주는 끝내주는 언어입니다.

만약 rustup을 통해서 Rust의 이전버전을 설치해놓은 상대라면, 업데이트는 아주 쉽습니다. 그냥 이렇게 치면 돼요.
rustup update stable

rustup을 설치한 적이 없다면, 우리 웹사이트의 설치 페이지에서 받을 수 있습니다. 그리고 깃허브에서 이번 버전에 대한 릴리즈 노트를 참조해보세요.

미래의 릴리즈를 테스트해서 러스트 팀을 돕고 싶다면, 로컬에서 베타 채널(rustup default beta) 또는 nightly 채널(rustup default nightly)로 업데이트하는 것을 고려할 수 있습니다.

버그를 발견했다면 리포트해주세요!




1.66.0 stable에는 무엇이 있나요?


필드가 있는 enum에 대한 명시적 판별(discriminants)

정수 표현(repr)이 달린 열거형은 이제 필드가 있는 경우에도 명시적 판별 값을 사용할 수 있습니다.

**#[repr(u8)] **
**enum Foo { **
** A(u8), **
** B(i8), **
** C(bool) = 42, **
**} **

이전에도 repr이 달린 enum에 명시적 판별 값을 사용할 수는 있었지만, 해당 variant들에 필드가 없는 경우에만 가능했습니다.

enum의 표현(repr)이 두 언어에서 일치해야 하는 언어 경계를 넘어 값을 전달할 때 명시적 판별자가 유용합니다.
명시적 판별 값은 다른 언어 사이에서 enum 값을 전달할 때 유용합니다.

예를 들면, 이런거죠.

**#[repr(u8)] **
**enum Bar { **
** A, **
** B, **
** C = 42, **
** D, **
}

여기서 Bar enum은 u8과 동일한 레이아웃을 갖는 것이 보장됩니다. 그리고 Bar::C variant는 판별값 42를 갖죠.

명시적으로 지정된 값이 없는 variant에는 소스 코드의 순서에 따라 자동으로 할당되는 판별값이 있습니다.
그래서 저기서 Bar::A는 0, Bar::B는 1, Bar::D는 43입니다.

이 기능을 쓰지 않고 Bar::C의 명시적 값을 설정하려면 그 앞에 41개의 불필요한 variant을 추가할 수밖에 없었어요..!

참고로 필드가 없는 enum의 경우 as 캐스팅을 통해 판별자를 검사할 수 있는데요. (예: Bar::C as u8)

Rust는 아직 필드가 있는 enum의 raw 판별값에 액세스하는 언어 수준의 방법을 제공하지 않습니다.
대신 현재 unsafe 코드를 사용하여 필드가 있는 enum의 판별값을 검사해야 합니다.

이 기능은 unsafe 코드를 필요한 언어간 FFI와 함께 사용하기 위한 것이니 추가적인 부담이 되지 않기를 바랍니다.

만약 판별값에 대한 불투명한 핸들이 필요하다면 std::mem::discriminant 함수를 참조하세요.




core::hint::black_box

컴파일러에서 생성된 머신코드를 벤치마킹하거나 검사할 때면은, 특정 위치에서 최적화가 발생하지 않도록 방지하는 것이 가끔은 유용합니다.

다음 예에서 push_cap 함수는 Vec::push를 루프로 4번 실행합니다.

**fn push_cap(v: &mut Vec) { **
** for i in 0..4 { **
** v.push(i); **
** } **
**} **

**pub fn bench_push() -> Duration { **
** let mut v = Vec::with_capacity(4); **
** let now = Instant::now(); **
** push_cap(&mut v); **
** now.elapsed() **
**} **

x86_64에서 컴파일러의 최적화된 출력을 검사하면 코드가 다소 짧게 보일 겁니다.

**example::bench_push: **
**sub rsp, 24 **
**call qword ptr [rip + std::time::Instant::now@GOTPCREL] **
**lea rdi, [rsp + 8] **
**mov qword ptr [rsp + 8], rax **
**mov dword ptr [rsp + 16], edx **
**call qword ptr [rip + std::time::Instant::elapsed@GOTPCREL] **
**add rsp, 24 **
**ret **

사실은, 우리가 벤치마킹하고 싶었던 push_cap 함수 전체가 최적화된거에요!

새로 안정화된 black_box 기능을 사용하면 이 문제를 해결할 수 있습니다.
기능적으로 black_box는 그다지 신기한건 아닙니다. 값을 그냥 바로 다시 전달할 뿐이거든요.

하지만 컴파일러는 black_box를 보면 입력으로 무엇이든 받을 수 있고 모든 값을 반환하는 함수로 취급합니다.

이것은 위에서 발생한 최적화를 비활성화하는 데 매우 유용합니다.
예를 들어 for 루프가 반복될 때마다 벡터가 실제로 무언가에 사용될 것임을 컴파일러에 암시할 수 있습니다.

**use std::hint::black_box; **
**fn push_cap(v: &mut Vec) { **
** for i in 0..4 { **
** v.push(i); **
** black_box(v.as_ptr()); **
** } **
**} **

이제 최적화된 어셈블리 출력에서 unroll된 for 루프를 찾을 수 있습니다.

**mov dword ptr [rbx], 0 **
**mov qword ptr [rsp + 8], rbx **
**mov dword ptr [rbx + 4], 1 **
**mov qword ptr [rsp + 8], rbx **
**mov dword ptr [rbx + 8], 2 **
**mov qword ptr [rsp + 8], rbx **
**mov dword ptr [rbx + 12], 3 **
**mov qword ptr [rsp + 8], rbx **

이 어셈블리 출력에서는 black_box를 호출의 사이드이펙트를 볼 수 있습니다.
mov qword ptr [rsp + 8], rbx 명령은 계속 쓸데없이 반복되죠. 이 인스트럭션은 실제로는 호출되지 않는 함수의 첫 번째 인수로 주소 v.as_ptr()를 씁니다.

생성된 코드는 push call에 의해 introduce된 할당의 가능성과 전혀 관련이 없습니다.
이는 컴파일러가 여전히 bench_push 함수에서 Vec::with_capacity(4)를 호출했다는 사실을 사용하고 있기 때문입니다.

black_box의 배치를 가지고 놀거나 여러 위치에서 사용하면 컴파일러 최적화에 미치는 영향을 확인할 수 있습니다.




cargo remove

Rust 1.62.0에서는 프로젝트에 종속성을 추가하는 명령행 도구인 cargo add를 공식 도입했었죠.

이제는 cargo remove를 통해 반대로 종속성을 제거할 수도 있습니다!




Stable이 된 API

다음 요소들은 이제 stable입니다.

proc_macro::Span::source_text

u*::{checked_add_signed, overflowing_add_signed, saturating_add_signed, wrapping_add_signed}

i*::{checked_add_unsigned, overflowing_add_unsigned, saturating_add_unsigned, wrapping_add_unsigned}

i*::{checked_sub_unsigned, overflowing_sub_unsigned, saturating_sub_unsigned, wrapping_sub_unsigned}

BTreeSet::{first, last, pop_first, pop_last}
BTreeMap::{first_key_value, last_key_value, first_entry, last_entry, pop_first, pop_last}

WASI에서 stdio lock 타입에 대한 AsFd 구현 추가

impl TryFrom<Vec> for Box<[T; N]>
core::hint::black_box
Duration::try_from_secs_{f32,f64}
Option::unzip
std::os::fd




기타 변경점

Rust 1.66 릴리스에는 다음과 같은 기타 변경 사항이 있습니다.

  1. 이제 패턴에서 ..=x 범위를 사용할 수 있습니다.
    2 Linux 빌드는 이제 LTO 및 BOLT를 사용하여 rustc 프론트엔드 및 LLVM 백엔드를 각각 최적화, 런타임 성능과 메모리 사용량을 모두 개선합니다.

그리고 RustCargoClippy에서 무엇이 바뀌었는지 확인해보세요.




1.66.0의 컨트리뷰터들

1.66.0의 완성엔 수많은 사람들이 함께했습니다. 전부 여러분이 없었다면 불가능했을 거에요.
고마워요!