[Rust] 1.86.0 업데이트 발표 (번역)
https://blog.rust-lang.org/2025/04/03/Rust-1.86.0.html
러스트는 누구든 믿음직하고 효과적인 소프트웨어를 만들 수 있게 도와주는 끝내주는 언어입니다.
만약 rustup을 통해서 Rust의 이전버전을 설치해놓은 상대라면, 업데이트는 아주 쉽습니다. 그냥 이렇게 치면 돼요.
rustup update stable
rustup을 설치한 적이 없다면, 우리 웹사이트의 설치 페이지에서 받을 수 있습니다. 그리고 깃허브에서 이번 버전에 대한 릴리즈 노트를 참조해보세요.
미래의 릴리즈를 테스트해서 러스트 팀을 돕고 싶다면, 로컬에서 베타 채널(rustup default beta) 또는 nightly 채널(rustup default nightly)로 업데이트하는 것을 고려할 수 있습니다.
버그를 발견했다면 리포트해주세요!
1.86.0 stable에는 무엇이 있나요?
Trait upcasting
이번 릴리스에는 오랫동안 기다려온 기능, trait object를 upcast하는 기능이 포함되어 있습니다.
trait이 supertrait을 가지고 있다면, 그 참조를 supertrait의 trait object에 대한 참조로 강요(coerce)할 수 있습니다.
trait Trait: Supertrait {}
trait Supertrait {}
fn upcast(x: &dyn Trait) -> &dyn Supertrait {
** x**
}
이건 Arc
이전에는 fn as_supertrait(&self) -> &dyn Supertrait와 같이 trait 자체에서 업캐스트 메서드를 제공하는 형태의 우회가 필요했고, 이는 한 종류의 참조/포인터에만 작동합니다. 이런 방법은 더 이상 필요하지 않습니다.
이것은 trait object에 대한 raw 포인터가 non-trivial invariant를 carry한다는 것을 의미하는데요. 그래서 유효하지 않은 vtable이 있는 trait object에 대한 raw 포인터를 safe 코드로 "leaking"하면 Undefined Behavior가 발생할 수 있습니다.
다만, 잘 통제된 상황에서 일시적으로 이러한 raw 포인터를 생성하는 것이 즉각적으로 Undefined Behavior를 유발하는지 여부는 아직 결정되지 않았습니다. 그래서 일단은 코드를 작성할 때 어떤 조건에서도 이러한 포인터를 생성하는 것을 자제해야 합니다. (그리고 Miri는 그것을 시행합니다).
trait 업캐스팅은 Any trait에 특히 유용할 수 있습니다. trait method를 추가하거나 external crate를 사용하지 않고 trait object를 dyn Any로 업캐스팅해서 Any의 다운캐스팅 method을 호출할 수 있기 때문이죠.
use std::any::Any;
trait MyAny: Any {}
impl dyn MyAny {
** fn downcast_ref
** (self as &dyn Any).downcast_ref()**
** }**
}
Rust 레퍼런스에서 trait upcasting에 대해 더 알아볼 수 있습니다.
HashMaps과 slices는 이제 수정 가능한 multiple elements 인덱싱을 제공합니다.
borrow checker는 get_mut의 반복 호출에서 얻은 참조를 동시에 사용하는 것을 방지합니다.
이 패턴을 안전하게 지원하기 위해 표준 라이브러리는 이제 슬라이스와 해시맵에 get_disjoint_mut 헬퍼를 제공하여 여러 요소에 대한 변경 가능한 참조들을 동시에 받아올 수 있습니다.
slices::get_disjoint_mut의 API docs에서 가져온 예를 참조하세요.
let v = &mut [1, 2, 3];
if let Ok([a, b]) = v.get_disjoint_mut([0, 2]) {
** a = 413;*
** b = 612;*
}
assert_eq!(v, &[413, 2, 612]);
if let Ok([a, b]) = v.get_disjoint_mut([0..1, 1..3]) {
** a[0] = 8;**
** b[0] = 88;**
** b[1] = 888;**
}
assert_eq!(v, &[8, 88, 888]);
if let Ok([a, b]) = v.get_disjoint_mut([1..=2, 0..=0]) {
** a[0] = 11;**
** a[1] = 111;**
** b[0] = 1;**
}
assert_eq!(v, &[1, 11, 111]);
safe function이 #[target_feature] attribute로 표시될 수 있음.
이전에는 target feature이 활성화되지 않은 상태에서 이러한 함수를 호출하는 것이 건전하지 않기 때문에 unsafe 함수만 #[target_feature] 속성으로 표시할 수 있었습니다.
이 릴리스는 target_feature_11 기능을 안정화하여 #[target_feature] 속성을 safe 함수에도 표시할 수 있습니다.
target feature attribute로 표시된 safe 함수는 target feature attribute로 표시된 다른 함수에서만 안전하게 호출할 수 있습니다.
하지만 Fn* trait로 bound되는 제네릭 타입을 사용하는 함수로는 전달될 수 없으며, target_feature 속성으로 표시된 함수 내부의 함수 포인터로 강제(coerce)되게 하는 정도의 지원만 제공합니다.
target feature attribute로 표시되지 않은 함수 내부에서도 unsafe 블록을 통해 호출할 수 있지만, target feature를 사용할 수 있는지 확인하는 것은 호출자의 책임입니다.
#[target_feature(enable = "avx2")]
fn requires_avx2() {
** // ... snip**
}
#[target_feature(enable = "avx2")]
fn safe_callsite() {
** **** // Calling requires_avx2 here is safe as safe_callsite**
** // requires the avx2 feature itself.******
** requires_avx2();**
}
fn unsafe_callsite() {
** **** // Calling requires_avx2 here is unsafe, as we must**
** // ensure that the avx2 feature is available first.******
** if is_x86_feature_detected!("avx2") {**
** unsafe { requires_avx2() };**
** }**
}
자세한 내용은 target_features_11 RFC에서 확인할 수 있습니다.
soundness가 필요한 경우 포인터가 non-null인지를 검사하는 Debug assertions
컴파일러는 이제 포인터가 non-zero 읽기와 쓰기 시, 그리고 포인터가 참조로 reborrow될때 포인터가 not null이라는 debug assertions을 삽입할 겁니다. 예를 들어, 다음 코드는 debug assertions이 활성화될 때 non-unwinding panic을 생성합니다.
*let _x = std::ptr::null::
*let _x = &std::ptr::null::
이와 같은 사소한 예제도 Rust 1.53.0부터 경고를 생성했습니다. 새로운 런타임 검사는 복잡성에 관계없이 이러한 시나리오를 감지할 겁니다.
이러한 assertions은 debug assertions이 활성화될 때만 발생하며, 이는 soundness에 의존해서는 안 된다는 것을 의미합니다.
또한 debug assertions을 비활성화하여 컴파일된 종속성(표준 라이브러리 등)이 debug assertions을 활성화한 코드에 의해 호출되는 경우에도 assertions을 트리거하지 않습니다.
missing_abi lint warn을 기본값으로
extern blocks과 함수(extern {}과 extern fn)에서 ABI를 생략하면 이제 경고가 발생합니다.
extern 키워드 후 ABI를 생략하는 것은 항상 암묵적으로 "C" ABI로 귀결되었습니다.
하지만 이제 "C" ABI를 명시적으로 지정하는 것이 권장됩니다. extern "C" {}나 extern "C" fn 같은 식으로요.
자세한 내용은 Explicit Extern ABIs RFC에서 확인하실 수 있습니다.
Target deprecation 경고
The tier-2 target i586-pc-windows-msvc will be removed in the next version of Rust, 1.87.0. Its difference to the much more popular i686-pc-windows-msvc is that it does not require SSE2 instruction support, but Windows 10, the minimum required OS version of all windows targets (except the win7 targets), requires SSE2 instructions itself.
티어-2 타겟 i586-pc-window-msvc는 러스트의 다음 버전인 1.87.0에서 제거될 겁니다.
훨씬 더 인기 있는 i686-pc-window-msvc와의 차이점은, SSE2 명령어 지원이 필수가 아니라는 겁니다. 하지만 모든 윈도우 타겟(win7 타겟 제외)의 최소 요구 OS 버전인 윈도우 10은 SSE2 명령어 자체가 필요합니다.
현재 i586-pc-window-msvc를 대상으로 하는 모든 사용자는 1.87.0 릴리즈 전에 i686-pc-window-msvc로 이동해야 합니다.
자세한 내용은 Major Change Proposal에서 확인하실 수 있습니다.
Stable이 된 API
{float}::next_down
{float}::next_up
<[_]>::get_disjoint_mut
<[_]>::get_disjoint_unchecked_mut
slice::GetDisjointMutError
HashMap::get_disjoint_mut
HashMap::get_disjoint_unchecked_mut
NonZero::count_ones
Vec::pop_if
sync::Once::wait
sync::Once::wait_force
sync::OnceLock::wait
다음 API들은 이제 const context에서도 사용 가능하다.
hint::black_box
io::Cursor::get_mut
io::Cursor::set_position
str::is_char_boundary
str::split_at
str::split_at_checked
str::split_at_mut
str::split_at_mut_checked
기타 변경점
이외의 모든 변경사항은 각각 Rust, Cargo, Clippy에서 확인하세요.
1.86.0의 컨트리뷰터들에게
1.86.0의 완성엔 수많은 사람들이 함께했습니다. 전부 여러분이 없었다면 불가능했을 거에요.