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

https://blog.rust-lang.org/2024/10/17/Rust-1.82.0.html
러스트는 누구든 믿음직하고 효과적인 소프트웨어를 만들 수 있게 도와주는 끝내주는 언어입니다.

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

rustup update stable

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

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

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




What's in 1.82.0 stable


cargo info

cargo는 이제 레지스트리에 있는 패키지 정보를 표시할 수 있는 info subcommand를 가지게 되었고, 10주년을 앞두고 오랜 요청을 이행합니다!

이런 여러 서드파티 확장들은 수년에 걸쳐 작성되었는데요. cargo info의 경우에는 병합 전에는
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 또한 보고해줍니다.




Apple target 출시


macOS 64-bit ARM는 이제 티어 1

64비트 ARM(애플 실리콘) macOS용 러스트 타겟 aarch64-apple-darwin은 현재 1티어 타겟으로, 제대로 작동하는 것에 대한 최고 보장을 나타냅니다.

플랫폼 지원 페이지에서 설명하는 바와 같이, Rust 레포지토리의 모든 변경 사항은 병합되기 전에 모든 1티어 타겟에 대한 전체 테스트를 통과해야 합니다.

이 타겟은 Rust 1.49부터 2티어로 소개되어서 rustup으로 사용할 수 있었습니다.

이 새로운 마일스톤은 aarch64-apple-darwin 타겟을 64비트 ARM 리눅스와 X86 macOS, 리눅스, 윈도우즈 타겟과 동등하게 만듭니다.



Mac Catalyst 타겟들은 이제 티어 2

Mac Catalyst는 애플이 iOS 애플리케이션을 맥에서 네이티브로 실행할 수 있게 해주는 기술입니다.

이건 cargo test --target=aarch64-apple-ios-macabi --target=x86_64-apple-ios-macabi가 대부분 작동하기 때문에, iOS에서 특정 코드를 테스트할 때 특히 유용합니다.
iOS 타겟에서 실제 디바이스나 시뮬레이터로 실행하려면, 추가 도구를 사용해서 번들로 묶어야 하니까요.

이 타겟은 2티어이며, rustup target add aarch64-apple-ios-macabi x86_64-apple-ios-macabi로 다운로드할 수 있습니다.
CI 파이프라인을 업데이트해서 코드가 iOS와 유사한 환경에서도 실행되는지 테스트하기에 좋죠.




Precise capturing use<..> 신택스

Rust는 이제 impl Trait bounds 내에서 use<..> 어떤 generic lifetime 파라미터가 캡처되는지 제어하는 문법을 지원합니다.

Rust의 Return-position impl Trait(RPIT) 타입들은 특정 제너릭 파라미터를 캡처합니다.
제니릭 파라미터를 캡처하면 해당 파라미터를 hidden 타입에서도 사용할 수 있고, 결과적으로 borrow checking에 영향을 미치죠.

2021년이나 그 이전 에디션에서 lifetime 파라미터는 불투명(opaque) 타입에서 문법적으로 언급되지 않는 한, bare functions나 inherent impl의 함수나 메서드에 대한 불투명 타입에서 캡처되지 않았습니다.

그러니까, 에러가 났단 거죠.

//@ 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 }

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

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


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

이건 "캡쳐 트릭"이라고 불렸고, 약간 baroque하고 뭐시기했습니다. 이제는 더 이상 필요하지 않습니다.

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

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

이런 간단한 경우에, 이 트릭은 RFC 3498에서 설명된 미묘한 이유로 + use<'_>와 정확히 동등합니다.
그러나 실제 사례에서는 return된 불투명 타입의 bound를 과도하게 제한하여 문제가 발생하곤 합니다.

예를 들어, 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를 제거할 수 없습니다. 왜냐하면 lifetime은 hidden 타입으로 사용되어야 하고, 캡처도 되어야 하기 때문입니다.

근데 우리는 'a: 'cx'의 bound를 추가할 수도 없습니다. 왜냐하면 이 lifetime들은 실제로 관련이 없고 'a가 'cx'보다 오래 지속된다는 것은 일반적으로 사실이 아닐 것이기 때문입니다.

대신 + <'cx, 'a>를 쓰면 잘 작동하고 올바른 bound를 가질 겁니다.

지금 우리가 안정화하고 있는 것에는 몇 가지 한계가 있습니다.

  1. use<..> 구문은 현재 trait 안이나 trait impl 안에서 나타날 수 없습니다.
  1. 그리고 use<..> 구문은 모든 in-scope 제너릭 타입이나 const 파라미터를 나열해야 합니다.

시간이 지나면 이러한 제한이 사라지기를 바랍니다.

2024 러스트에서 위의 예제들은 구문이나 트릭을 사용할 필요 없이 "그냥 작동"할 겁니다.
새로운 에디션에서는 불투명한 타입이 모든 lifetime 파라미터 scope 내에서 자동으로 캡처하기 때문입니다.

이게 더 나은 default이며, 우리는 이것이 코드를 깔끔하게 정리하는지에 대한 많은 증거를 보았습니다.
2024년 러스트에서는 use<..> 구문이 그 default를 opt out하는 중요한 방법을 제공할 것입니다.

use<..> 구문, 캡처, 그리고 이게 Rust 2024에 어떻게 적용되는지에 대한 자세한 내용은 에디션 가이드의 "RPIT lifetime 캡처 규칙" 장을 참조해주세요.

전반적인 방향에 대한 자세한 내용은 최근 블로그 게시물 "2024 러스트의 impl trait 변경"을 참조해주세요.




raw 포인터 생성을 위한 Native syntax

unsafe 코드는 때때로 댕글링되거나, align이 잘못되거나, 유효한 데이터를 가리키지 않을 수 있는 포인터를 처리해야 합니다.

이게 일반적으로 발생하는 경우는 repr(packed) 구조체입니다.
이러한 경우 undefined behavior를 유발할 수 있으므로 참조의 생성을 피하는 것이 중요합니다.

그러니까, 참조를 생성하는 일반 &와 &mut 연산자를 사용할 수 없다는 것을 의미합니다.
참조가 즉시 raw 포인터로 캐스팅되더라도 undefined behavior를 피하기에는 너무 늦습니다.

몇 년 동안 매크로 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() };**
}

이 native syntax는 이러한 연산자의 피연산자 표현식이 place expression으로 해석된다는 것을 더 명확하게 합니다.

또한 포인터를 생성하는 동작을 언급할 때 "address-of"라는 용어를 피하기도 하죠.
러스트는 포인터와 주소의 잘못된 동등성을 재확인하는 "address-of"와 같은 용어에서 벗어나고 있습니다. 포인터는 단순한 주소 이상의 의미를 가지기 때문이죠.




Safe items with unsafe extern

Rust 코드는 foreign 코드의 함수와 static을 사용할 수 있습니다.
이러한 foreign items의 타입 시그너처는 extern 블록으로 제공됩니다.

역사적으로 extern 블록 내의 모든 항목은 사용이 unsafe했지만, extern 블록 자체에 unsafe를 쓸 필요는 없었습니다.

하지만 extern 블록 내의 시그너처가 잘못되면 해당 항목을 사용시에 undefined behavior가 발생합니다.
이게 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 블록 내의 항목을 safe한 것으로 표시할 수 있다는 것입니다.

그래서 위의 예에서는 unsafe 없이 sqrt를 호출하거나 TAU를 읽을 수 있습니다.

safe나 unsafe로 표시되지 않은 항목은 보수적으로 안전하지 않다고 가정한다.

앞으로의 릴리즈에서는 lints를 통해 unsafe extern을 사용하도록 권장할 것입니다.
2024 러스트부터는 unsafe extern을 사용해야 합니다.

자세한 내용은 RFC 3484와 에디션 가이드의 "unsafe extern 블록" 장을 참조해주세요.




Unsafe attributes

no_mangle과 같은 일부 Rust attribute는 unsafe 블록 없이 undefined behavior를 유발하는 데 사용될 수 있습니다.

이것이 일반 코드라면, unsafe {} 블록에 배치하도록 요구하지만, 아직 attribute에 대해서는 대처할 수 있는 구문이 없었습니다.

이러한 attribute가 러스트의 safe 보장을 훼손할 수 있다는 사실을 반영하기 위해, 이제 "unsafe"로 간주되며 다음과 같이 작성되어야 합니다.

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

unsafe가 없던 attribute의 오래된 형태는 현재 여전히 허용되고 있지만, 미래의 어느 시점에는 이의를 제기할 수 있으며, 2024 러스트에서는 큰 오류가 될 것입니다.

영항을 받는 attribute는 다음과 같습니다.

자세한 내용은 에디션 가이드의 "Unsafe attributes" 장을 참조해주세요.




패턴 매칭에서의 empty types 생략

empty(혹은 uninhabited) 타입을 값으로 match하는 패턴은 이제 생략할 수 있습니다.

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

이건 enum Void {} 처럼 variant가 없는 empty 타입, 또는 가시적인 empty 필드와 #[non_exhaustive] 속성이 없는 구조체/enum 같은 empty 타입에서 작동합니다.

또한 이 타입은 현재 여전히 unstable하지만 never 타입 "!"과 결합하면 특히 유용할 것입니다.

초기화되지 않은 값이나 unsafe 코드와 관련된 경우에는 패턴 생략이 허용되지 않습니다.
여기서 unsafe하다는 것은 참조, 포인터 또는 uniom 필드를 통해 empty 타입에 접근하는 경우를 말합니다.

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

여러 러스트 버전을 지원하려는 crate를 방해하지 않기 위해 empty 패턴이 있는 match arms은 제거할 수 있음에도 불구하고 아직 "unreachable 코드" 경고로 보고되지 않습니다.




부동소수점 NaN semantics과 const

알다시피 부동 소수점 값(f32, f64)에 대한 연산은 껄쩍지근한 부분이 많죠.

그 이유 중 하나는 0.0 / 0.0 같은 결과를 나타내는 데 사용되는 NaN("not a number") 값의 존재입니다.

NaN 값을 더욱 거시기하게 만드는 것은 하나 이상의 가능한 NaN 값이 존재한다는 거죠. 게다가 서로 다 달라요!

NaN 값에는 부호도 있고, 페이로드도 있죠. 각각 f.is_sign_positive(), f.to_bits()로 가져올 수 있습니다.

근데, NaN 값의 부호와 페이로드는 모두 항상 거짓을 반환하는 ==에 의해 완전히 무시됩니다.

하드웨어 아키텍처 전반에 걸쳐 부동 소수점 연산의 동작을 표준화하려는 매우 성공적인 노력에도 불구하고, NaN이 언제 양수 또는 음수인지와 정확한 페이로드가 무엇인지에 대한 세부 사항은 아키텍처에 따라 다릅니다.

근데 문제를 더욱 복잡하게 만드는 친구들이 있죠.
Rust와 LLVM 백엔드는 정확한 numeric 결과가 변경되지 않음이 보장될 때 부동 소수점 연산에 최적화를 적용하지만, 이러한 최적화는 생성되는 NaN 값을 변경할 수 있습니다.

예를 들어, f * 1.0은 f에만 최적화될 수 있습니다. 그런데 f가 NaN이면 그 결과의 정확한 비트 패턴을 변경할 수 있어요!

이 릴리스를 통해서 러스트는 NaN 값이 어떻게 작동하는지에 대한 일련의 규칙을 표준화합니다.

이 규칙 세트는 완전히 결정론적이지는 않으며, (0.0 / 0.0).is_sign_positive()와 같은 연산의 결과가 하드웨어 아키텍처, 최적화 수준 및 주변 코드에 따라 다를 수 있음을 말합니다.

예를 들어, 완전히 portable한 것을 목표로 하는 코드는 to_bit를 사용하는 것을 피해야 하며, f.is_sign_positive() 대신 f.signum() ==1.0을 사용해야 합니다.

그러나 이 규칙은 NaN boxing과 같은 진화된 데이터 표현 기술이 Rust 코드로 구현될 수 있도록 신중하게 선택됩니다.

정확한 규칙에 대한 자세한 내용은 문서를 확인해주세요.

NaN 값에 대한 의미가 정착되면, 이 릴리스는 또한 const fn에서의 부동 소수점 연산을 허가할 수 있습니다.

위에서 설명한 이유로 (0.0 / 0.0).is_sign_positive()와 같은 작업은 컴파일타임과 런타임에서 실행될 때 다른 결과를 생성할 수 있습니다.

이것은 버그가 아니며, 코드는 항상 정확히 같은 결과를 생성하는 const fn에 의존해서는 안 됩니다.




assembly에 즉시 상수 사용

const 어셈블리 피연산자는 이제 정수를 레지스터에 먼저 저장하지 않고, 즉시 사용할 수 있는 방법을 제공합니다.

예를 들어, 직접 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!

Playground link.

위에서 LEN = const MSG.len()과 같은 구문은 format specifier 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

자세한 내용은 레퍼런스를 참조해주세요.




Safe하게 unsafe statics을 addressing하기

이 코드는 이제 허용됩니다.

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은 place expression입니다.

이전에 컴파일러의 안전성 검사는 raw ref 연산자가 실제로 피연산자의 위치에 영향을 미치지 않는다는 것을 알지 못했기 때문에, 포인터에 대한 읽기나 쓰기를 가능한 것으로 취급했습니다.

하지만 실제로는 포인터를 생성하기 때문에 unsafe한 것은 없습니다.

이를 완화하면, "unused_unsafe" lint를 거부했을때 일부 unsafe 블록이 이제 사용되지 않은 것으로 보고되는 문제가 발생할 수 있지만, 이제는 이전 버전에서만 유용합니다.

이 예제에서와 같이 Rust의 여러 버전을 지원하려면 #[allow(unused_unsafe)]로 이러한 unsafe 블록에 attribute를 달아주세요.

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의 미래 버전에서는 이것을 static뿐만 아니라 이 위치에서 safe한 다른 표현식까지 일반화할 것으로 기대합니다.




Stable이 된 API들

std:🧵:Builder::spawn_unchecked
std::str::CharIndices::offset
std::option::Option::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::Ready::into_inner
std::iter::repeat_n
impl<T: Clone> DoubleEndedIterator for Take<Repeat>
impl<T: Clone> ExactSizeIterator for Take<Repeat>
impl<T: Clone> ExactSizeIterator for Take<RepeatWith>
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
Box::new_uninit
Box::assume_init
Box<[T]>::new_uninit_slice
Box<[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

다음 APIs들은 이제 const contexts에서도 사용 가능합니다.

std::task::Waker::from_raw
std::task::Context::from_waker
std::task::Context::waker
$integer::from_str_radix
std::num::ParseIntError::kind




기타 변경점

이외의 모든 변경사항은 각각 Rust, Cargo, Clippy에서 확인하세요.




1.82.0의 컨트리뷰터들에게

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

고마워요!