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

[원본 링크]

https://blog.rust-lang.org/2026/11/12/Rust-1.100.0

러스트는 누구든 믿음직하고 효과적인 소프트웨어를 만들 수 있게 도와주는 끝내주는 언어입니다.

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

rustup update stable

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

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

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




1.100.0 stable에는 무엇이 있나요?


Never 타입

10년의 시간과 5번의 좌초 끝에, 마침내 never 타입이 안정화되었습니다!
이로써 이번 작업의 프로젝트 목표도 모두 달성되었습니다.

never 타입은 값을 가질 수 없는 타입(uninhabited type)의 대표적인 형태이며, 코드에서 "!"로 표기됩니다.

이 타입은 발산하는 연산(diverging computations), 즉 어떤 값으로도 평가가 완료되지 않는 연산을 나타내며, 이러한 연산 뒤에 오는 코드는 도달할 수 없습니다.

"return", "break", "std::process::exit"처럼 제어 흐름을 종료하는 표현식 역시 "!" 타입을 갖습니다.

never 타입의 값을 생성하는 것은 불가능하므로, Rust는 never-to-any 강제 형 변환(coercion)을 허용합니다.
이를 통해 "!" 타입을 다른 어떤 타입으로든 변환할 수 있습니다. 이 특성이 never 타입을 다른 값이 없는 타입들과 구별해줍니다.

예를 들어, 이 특성을 활용하면 "match"나 "if"의 특정 분기에서 값을 반환하지 않아도 됩니다.

fn mrrrow(option: Option) {
** let value = match option {**
** **** // x has type u32******
** Some(x) => x,**
** **** // return has type !, which is then coerced to u32,**
** // allowing the match to pass type checking.******
** None => return,**
** };**
** **** // ...******
}

기존에도 never 타입을 이러한 방식으로 사용하거나 확인할 수 있었고, 함수의 반환 타입("-> !")으로 명시하는 것도 가능했습니다.
하지만 이번 릴리스부터는 코드의 어느 위치에서든 never 타입을 명시적으로 사용할 수 있게 되었습니다.

fn infallible_unwrap(res: Result<T, !>) -> T {
** // ^^ writing Result<_, !> was not allowed before**
** let Ok(v) = res;**
** v**
}

또한 이번 릴리스에서는 "Infallible"을 never 타입("!")의 별칭(alias)으로 변경했습니다. 이에 따라 기존에 "Infallible"을 사용하던 모든 API에서 "!" 타입을 호환하여 사용할 수 있게 되었습니다.

이는 "Infallible"이 처음 도입되었을 때부터 계획된 변경 사항이며, never 타입의 안정화가 그토록 어려웠던 이유 중 하나이기도 합니다. (waffle은 안정화 과정에서 겪었던 몇 가지 어려움에 대해 발표한 바 있습니다.)

안타깝게도 "Infallible"을 변경하기 위해서는 하위 호환성을 깨뜨리는 변경(breaking change)을 도입해야 했습니다.

이번 릴리스에서는 2024 에디션에서 이미 적용된 것과 마찬가지로, 이전 에디션(2015, 2018, 2021)의 never 타입 폴백을 "()"에서 "!"로 변경했습니다.

대부분의 경우 이 변경으로 인한 차이를 체감하기는 어렵겠지만, 드물게 기존 코드에 영향을 줄 수 있습니다. 예를 들어, 제네릭 타입 매개변수를 반환하는 함수의 결과를 무시하는 경우가 이에 해당합니다.

또한 이 변경으로 인해 컴파일러가 이전보다 더 많은 코드를 도달할 수 없는 코드(unreachable code)로 판단할 수도 있습니다.

저희는 하위 호환성 변경으로 인한 영향을 최소화하기 위해 최대한 노력했습니다. 이를 위해 충분히 오래전부터 향후 호환성 경고(future compatibility warnings)를 제공했으며, 이번 변경으로 인해 문제가 발생한 공개 crate에 대해서도 수정 버전을 배포했습니다.

만약 의존성에서 never 타입과 관련된 문제로 빌드가 실패한다면, 이미 수정 버전이 배포되었을 가능성이 있으므로 해당 의존성을 업데이트해 보시기 바랍니다.




Allocator API

이번 릴리스에서는 "allocator_api"의 핵심 기능이 안정화되었습니다. 이에 따라 라이브러리 개발자는 특정 표준 라이브러리 타입과 함께 사용할 수 있는 커스텀 메모리 할당자(custom allocator)를 구현할 수 있게 되었습니다.

현재 지원되는 타입은 "Vec"과 "Box"이며, 새롭게 안정화된 "Allocator" trait을 통해 이를 사용할 수 있습니다.

이는 Allocators 1.0 프로젝트 목표를 달성하기 위한 기반이 됩니다.

아직 해야 할 작업은 많이 남아 있습니다.
사용자 정의 할당자와 함께 사용할 수 있는 API의 범위를 확장하고, 더 많은 컬렉션 및 포인터 타입에서 사용자 정의 할당자를 사용할 수 있도록 안정화하는 작업 등이 포함됩니다.

하지만 핵심 트레이트와 설계가 확립된 만큼, 앞으로 더 많은 관련 기능이 빠르게 안정화될 것으로 기대합니다!




DropGuard: 모든 종료 경로에서 클로저 실행

"Drop" trait 사용하면 변수가 스코프를 벗어날 때 코드를 실행할 수 있습니다.
이는 "return", "?" 연산자, "panic" 등 어떤 방식으로 스코프를 벗어나든 동일하게 적용됩니다.

"Drop"이 타입 자체에 통합되어 있기 때문에, Rust에서는 소켓을 명시적으로 닫을 필요가 없습니다.

하지만 단순히 함수가 종료될 때 특정 코드를 무조건 실행하고 싶은 경우, 직접 "Drop"을 구현하는 과정이 다소 번거로울 수 있습니다.

이를 해결하기 위해 core::mem::DropGuard를 새롭게 도입했습니다. "DropGuard"는 드롭될 때 클로저(closure)를 실행하는 새로운 헬퍼 타입입니다.

"DropGuard"는 변수와 클로저, 두 가지 인수를 받습니다. 또한 "Deref"와 "DerefMut"을 구현하므로, 기존과 동일한 방식으로 변수에 접근하고 수정할 수 있습니다.
하지만 "DropGuard"가 드롭되면 지정된 클로저를 실행하고 해당 변수를 소비(consume)합니다.

"DropGuard"는 "dismiss" 메서드도 제공합니다.
이 메서드를 사용하면 소멸자를 실행하지 않고 가드가 보유한 변수를 반환받을 수 있습니다.

이 기능을 활용하면 실패 시 롤백(rollback)하는 원자적(all-or-nothing) 트랜잭션 형태의 API를 간편하게 구현할 수 있습니다.

다음은 파싱에 실패했을 때 가드를 이용해 변경 사항을 롤백하는 파서(parser)의 구현 예제입니다.

use std::mem::DropGuard;
use std::num::ParseIntError;


/// Attempt to parse all strings and push them into nums.
/// If parsing any of them fails, nums remains unchanged.****
fn parse_all(nums: &mut Vec, strings: &[&str]) -> Result<(), ParseIntError> {
** let len = nums.len();**
** let mut nums = DropGuard::new(nums, move |nums| {**
** nums.truncate(len); // ← 1. Reset the vec on failure**
** });**


** for s in strings {**
** nums.push(s.parse()?); // ← 2. If ? returns an error, roll back**
** }**


** DropGuard::dismiss(nums); // ← 3. Parsing successful, dismiss the guard**
** Ok(())**
}




zero size 타입 생성하기

unsafe code에 대한 가이드라인이 아직 확정되지 않은 가운데, 크기가 0인 타입(Zero-Sized Type, ZST)을 unsafe 방식으로 생성한다는 것이 정확히 무엇을 의미하는지, 그리고 이러한 작업을 수행하려는 개발자에게 어떤 방법을 권장해야 하는지 명확히 정의할 필요가 생겼습니다.

Rust 1.100에서는 이 질문에 대한 해답으로 단 하나의 함수를 도입했습니다.

바로 std::mem::conjure_zst입니다.

기존에는 "()"을 "transmute"하거나, "MaybeUninit::uninit().assume_init()"을 사용하거나, 그 밖의 다양한 우회적인 방법으로 크기가 0인 타입의 값을 생성해야 했습니다.

하지만 이제는 해당 타입의 값을 생성해도 안전하다는 사실을 알고 있을 때, 이를 위해 특별히 마련된 함수를 사용할 수 있습니다.

값을 가질 수 있는 타입(inhabited type)의 크기가 0이라면, 그 값이 무엇이어야 하는지는 명확합니다. 바로 아무것도 없다는 것입니다.

물론 API는 안전성을 보장하기 위해 특정 타입의 값을 임의로 생성할 수 없다는 특성에 의존하는 경우가 많습니다. 바로 이러한 이유로 "conjure_zst"는 unsafe 함수로 제공됩니다.

예를 들어, PyO3의 ""Python<'py>"" (https://docs.rs/pyo3/0.30.1/pyo3/marker/struct.Python.html)는 Python의 GIL을 보유하고 있음을 나타내는 크기가 0인 마커 토큰(marker token)입니다.

Python의 많은 API는 안전하게 동작하기 위해 GIL을 보유하고 있어야 합니다.
하지만 이러한 토큰을 아무런 근거 없이 임의로 생성할 수 있다면, safe한 PyO3 호출에서도 GIL을 보유해야 한다는 필수 전제조건을 위반할 수 있게 됩니다.




Cargo에서 사용하지 않는 의존성 보고

더 이상 사용하지 않거나 특정 플랫폼에서만 필요한 의존성을 빌드하면 빌드 속도가 느려질 수 있습니다.

Rust 컴파일러인 "rustc"는 이러한 문제를 감지하기 위해 "unused_crate_dependencies" lint를 제공하지만, Cargo와 함께 사용할 경우 오탐(false positive)이 발생할 수 있습니다.

여러 빌드 타겟이 의존성을 공유할 수 있기 때문입니다. 예를 들어, "dependencies.tracing-subscriber"는 패키지 내 모든 바이너리("bin")에서 사용할 수 있지만, 실제로는 일부 바이너리에서만 사용될 수 있습니다.

이제 Cargo가 사용하지 않는 의존성을 직접 감지하여 사용자에게 알려줍니다.

예를 들면 다음과 같습니다.

warning: unused dependency tracing-subscriber
** --> Cargo.toml:23:1**
** |**
23 | tracing-subscriber.workspace = true
** | ^^^^^^^^^^^^^^^^^^**
** |**
** = note: cargo::unused_dependencies is set to warn by default**
help: consider removing the dependency on tracing-subscriber

이 경고에는 한 가지 제약이 있습니다. 의존성 테이블의 항목을 사용할 가능성이 있는 모든 빌드 타깃을 빌드해야만 경고가 표시된다는 점입니다.

예를 들어, "cargo check --bin foo"를 실행하더라도 사용하지 않는 "[dependencies]" 항목에 대한 경고는 표시되지 않습니다. 해당 의존성을 사용하는 다른 바이너리가 존재할 수 있기 때문입니다.

또한 사용하지 않는 개발 의존성("dev-dependencies")에 대해서는 경고가 표시되지 않습니다. 모든 개발용 빌드 타겟이 빌드되었는지 확인할 방법이 없기 때문입니다.

이번 경고 기능은 "Cargo.toml"의 "[lints.cargo]" 테이블을 통해 제어하는 새로운 Cargo lint 시스템의 일부로 도입되었으며, 이를 통해 해당 프로젝트 목표가 달성되었습니다.

새롭게 도입된 다른 린트들도 함께 살펴보시기 바랍니다.




Cargo 캐시 충돌 감소

Cargo는 모든 라이브러리와 바이너리를 빌드 디렉터리("build-dir")에서 빌드한 다음, 최종 빌드 산출물을 타깃 디렉터리("target-dir")로 옮깁니다. 기존에는 이 산출물들이 공통 상위 디렉터리를 공유했습니다.

"--features"와 같은 서로 다른 빌드 설정을 사용하면 빌드 디렉터리에 동일한 라이브러리나 바이너리의 여러 복사본이 생성될 수 있습니다.

이러한 복사본을 구분하기 위해 빌드 설정 해시(build-configuration hash)를 사용했지만, 플랫폼 제약으로 인해 해시가 항상 적용되지는 않았습니다.

이로 인해 불필요한 재빌드가 발생하거나, 필요한 재빌드가 누락되거나, 어떤 바이너리나 라이브러리가 실제로 사용되는지 혼동하는 등의 문제가 발생할 수 있었습니다.

특히 여러 Git worktree가 동일한 빌드 디렉터리를 공유하는 경우, 이러한 문제는 더욱 심각해졌습니다.

이제 빌드 디렉터리는 파일 유형이 아닌 빌드 단계(build step)를 기준으로 디렉터리 구조를 구성하고, 빌드 설정 해시를 파일 이름에서 상위 디렉터리 이름으로 옮기는 방식으로 변경되었습니다.

이를 통해 빌드 디렉터리의 모든 항목에 빌드 설정 해시가 일관되게 적용됩니다.

이 작업은 2025년 빌드 디렉터리 레이아웃(build directory layout) 프로젝트 목표의 일환으로 시작되었습니다. 이번 안정화는 새로운 프로젝트 목표를 향한 중요한 이정표이기도 합니다.

이를 바탕으로 향후 다음과 같은 개선 작업을 추진할 수 있게 되었습니다.

앞서 저희는 이번에 적용되는 변경 사항에 대해 테스트 참여를 요청한 바 있습니다.

이번 변경은 Cargo의 내부 구현에 관한 것이지만, 기존 기능의 부재나 불완전한 동작을 보완하기 위해 우회적인 해결책을 적용한 빌드 프로세스 및 테스트에 영향을 줄 수 있습니다.

일부 경우에는 기존의 우회적인 해결책이 더 이상 필요하지 않을 수도 있습니다.

가능한 해결 방법을 확인하거나 새로운 사용 사례를 제보하려면 "cargo#17287"을 참고하시기 바랍니다.




Cargo 의존성 쿨다운 (혹은 min-publish-age)

악성 코드가 포함된 패키지 버전은 배포된 후 수 시간에서 수일 이내에 자동화된 보안 스캐너나 개발자 커뮤니티에 의해 발견되는 경우가 많습니다.

Rust 1.100에서는 Cargo의 "min-publish-age"를 고려한 의존성 버전 결정 기능이 안정화되었습니다. 이 기능을 사용하면 새롭게 배포된 패키지 버전을 즉시 채택하지 않고, 일정 시간이 지난 후에만 의존성으로 선택하도록 설정할 수 있습니다.

이러한 방식을 의존성 쿨다운(Dependency Cooldowns)이라고도 합니다.

이 기능은 새로 배포된 패키지에 잠재적인 보안 문제가 있는지 확인할 시간을 확보함으로써, 악성 패키지가 프로젝트에 유입될 위험을 줄이는 데 도움이 됩니다.

다음과 같이 ".cargo/config.toml" 파일에서 설정하여 기능을 활성화할 수 있습니다.

[registry]
global-min-publish-age = "3 days"

이제 Cargo는 의존성 버전을 선택할 때 설정된 최소 게시 경과 기간("min-publish-age")이 지나지 않은 버전을 제외하고, 어떤 버전의 선택이 보류되었는지도 사용자에게 알려줍니다.

$ cargo update
Updating crates.io index
Locking 2 packages to highest compatible version as of min-publish-age
Adding cc v1.11.0 (available: v1.18.0, published 3 hours ago)
Adding clap 4.6.7 (available: 4.6.9, published 2 days ago)

새로운 버전이 즉시 필요한 경우에는 특정 업데이트에 한해 이 검사를 무시하도록 설정할 수 있습니다.

$ CARGO_RESOLVER_INCOMPATIBLE_PUBLISH_AGE=allow cargo update clap --precise 4.6.9
Updating crates.io index
Updating clap 4.6.7 -> clap 4.6.9 (published 2 days ago, minimum age 3 days)

이렇게 선택한 최신 버전은 "Cargo.lock"에 기록되며, 이후 "cargo update"를 실행하여 의존성 버전을 다시 결정하기 전까지 해당 버전이 유지됩니다.


주의사항

이 기능을 사용할 때는 다음과 같은 사항을 알아두어야 합니다.



향후 계획

Rust 생태계의 보안을 더욱 강화하기 위한 추가 작업도 계획되어 있습니다.

이러한 작업은 crates.io 공급망 보안 강화(crates.io Supply Chain Hardening) 프로젝트 목표의 일환으로 진행되며, 다음과 같은 기능이 포함됩니다.

이를 통해 Rust 패키지 생태계 전반의 공급망 보안을 한층 강화할 계획입니다.




Stable이 된 API

impl PartialEq<Cow<', [U]>> for Vec<T, A>
impl PartialEq<Cow<'
, [U]>> for &[T]
impl PartialEq<Cow<'_, [U]>> for &mut [T]
std::alloc::Allocator
std::alloc::Global
std::alloc::System
impl Allocator for Global
impl Allocator for System
impl<A: Allocator> Allocator for &A
impl<T: Allocator, A: Allocator> Allocator for Box<T, A>
impl<A: Allocator> Allocator for &mut A
impl<T: Allocator, A: Allocator> Allocator for Arc<T, A>
impl<T: Allocator, A: Allocator> Allocator for Rc<T, A>
Box::new_in
Box::from_raw_in
Box::from_non_null_in
Box::into_raw_with_allocator
Box::into_non_null_with_allocator
Box::allocator
Vec::new_in
Vec::with_capacity_in
Vec::from_raw_parts_in
Vec::from_parts_in
Vec::into_raw_parts_with_allocator
Vec::into_parts_with_allocator
Vec::allocator
Child::main_thread_handle
impl Default for std::sync::Once
bool::toggle
Box::take
Box::map
Rc::map
Arc::map
impl AsRef for !
impl AsMut for !
core::mem::DropGuard
core::mem::conjure_zst
UnsafeCell::replace
UnsafeCell::as_ref_unchecked
UnsafeCell::as_mut_unchecked
str::trim_prefix
str::trim_suffix
[T]::trim_prefix
[T]::trim_suffix
Path::trim_prefix
Vec::from_fn
os::windows::process::CommandExt::show_window




기타 변경점

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




1.100.0의 컨트리뷰터들에게

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

고마워요!


Search