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

[원본 링크]

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

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

rustup update stable

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

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

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




1.96.0 stable에는 무엇이 있나요?


새로운 Range* 타입

많은 사용자들이 Range나 이와 관련된 core::ops 타입들이 Copy Trait를 구현하고 있을 것이라 기대할테지만, 실제로는 그렇지 않습니다.
이 타입들은 Iterator를 직접 구현하고 있는데, 동일한 타입에 Iterator와 Copy를 모두 구현하는 것은 치명적인 실수(footgun)를 유발할 수 있기 때문에 지금까지 이를 피해왔습니다.

RFC 3550에서는 Iterator 대신 IntoIterator를 구현하여 Copy도 가능하게 만든 새로운 range 타입들을 제안했습니다.
현재 해당 RFC의 표준 라이브러리 작업 기능이 안정화되어 다음과 같은 타입들이 새롭게 도입되었습니다.

core::range::Range
core::range::RangeFrom
core::range::RangeInclusive
Associated iterators

향후 릴리즈될 러스트 버전에는 core::ops에서 re-export된 core::range::RangeFull, core::range::RangeTo가 추가될 예정입니다.
이 타입들은 Iterator를 구현하지 않으며, 이미 Copy를 구현하고 있습니다.

또한, 기존의 범위 타입들을 보관할 새로운 공간으로 core::range::legacy::*가 도입됩니다.
현재 0..1과 같은 range 구문은 당분간 기존의 레거시 타입을 생성하지만, 향후 새롭게 출시될 Edition부터는 core::range 타입들을 생성하도록 업데이트될 예정입니다.

이 기능들이 안정화되면서, 이제는 시작(start) 인덱스와 끝(end) 인덱스를 각각 분리할 필요 없이 Copy 트레이트가 구현된 타입 내에 슬라이스 range를 그대로 저장할 수 있게 되었습니다.

use core::range::Range;


#[derive(Clone, Copy)]
pub struct Span(Range);


impl Span {
** pub fn of(self, s: &str) -> &str {**
** &s[self.0]**
** }**
}

새로운 RangeInclusive는 iteration이 완료된 상태를 숨겨야 했던 레거시 버전과 달리, 필드를 public으로 공개합니다.
이 새 타입은 반복을 시작하려면 먼저 다른 타입으로 변환해야 해서 상태 노출이 문제가 되지 않거든요.

라이브러리 작성자들은 public API를 만들 때, 기존 range 타입과 새 range 타입을 모두 수용할 수 있도록 impl RangeBounds를 활용하는 것을 고려해야 합니다.
만약 구체 타입(concrete type)이 꼭 필요한 상황이라면, 향후 기본값으로 자리 잡게 될 새로운 range 타입을 우선적으로 사용하는 것이 좋습니다.




Assert matching patterns

새로운 매크로인 assert_matches!와 debug_assert_matches!는 값이 주어진 패턴과 일치하는지 확인하며, 일치하지 않을 경우 해당 값의 Debug 표현식을 출력하고 panic을 던집니다.

이 매크로들은 본질적으로 assert!(matches!(..)) 및 debug_assert!(matches!(..))와 동일하지만, 실패 시 값이 함께 출력되므로 실패 원인을 진단하기 훨씬 편해집니다.

이 새 매크로들은 standard prelude에 추가되지 않았어요. 동일한 이름의 매크로를 제공하는 인기 있는 서드파티 크레이트들과 충돌할 수 있기 때문이죠.
따라서 이 매크로들을 사용하려면 먼저 core/std에서 수동으로 import해야 합니다.

역주
prelude: use하지 않아도 전역 네임스페이스에 노출되는 숏컷 함수/타입들

use core::assert_matches;


/// Random Number****
fn get_random_number() -> u32 {
** **** // chosen by a fair dice roll.**
** // guaranteed to be random.******
** 4**
}


fn main() {
** assert_matches!(get_random_number(), 1..=6);**
}




WebAssembly target의 변경사항

이제 WebAssembly 타겟이 더 이상 링커에 --allow-undefined 플래그를 전달하지 않습니다. 이에 따라 링크 시점에 undefined symbols이 있을 경우, 기존처럼 env 모듈로부터의 WebAssembly import로 변환되는 대신 링커 오류가 발생합니다.

이번 변경은 버그를 더 일찍 잡아내고 심볼 네이밍 오류 등으로 인한 문제를 방지하기 위해, 링크와 관련된 모든 심볼이 정의되지 않으면 모듈이 링크되지 않도록 막아줍니다.

링크 시점에 undefined symbol이 나타나는 것은 대개 빌드 타임 버그나 설정 실수를 의미합니다.

하지만 의도적으로 기존 동작 방식을 유지하고 싶다면, RUSTFLAGS=-Clink-arg=--allow-undefined 환경 변수를 설정하거나, 소스 코드를 수정하여 해당 심볼을 정의하는 블록 위에 #[link(wasm_import_module = "env")] 어트리뷰트를 추가하면 이전 방식대로 동작합니다.

이 변경 사항은 이전에 블로그를 통해 예고된 바 있으며, 이번 Rust 1.96 버전부터 본격적으로 적용됩니다.




안정화된 API

assert_matches!
debug_assert_matches!
From for AssertUnwindSafe
From for LazyCell<T, F>
From for LazyLock<T, F>
core::range::RangeToInclusive
core::range::RangeToInclusiveIter
core::range::RangeFrom
core::range::RangeFromIter
core::range::Range
core::range::RangeIter




Cargo 취약점 2가지

Rust 1.96 버전에는 서드파티 레지스트리 사용자들을 위한 두 가지 취약점 수정 사항이 포함되어 있습니다.

공식 저장소인 crates.io 사용자들은 이 두 가지 취약점 모두에 영향을 받지 않습니다.




기타 변경점

이외의 모든 변경사항은 각 Rust, Cargo, Clippy 페이지를 확인하세요




1.96.0의 컨트리뷰터들에게

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

고마워요!