[Rust] 1.51.0 업데이트 발표 (번역)
https://blog.rust-lang.org/2021/03/25/Rust-1.51.0.html
우리 러스트 팀은 새 버전 [1.51.0]을 발표하게 돼서 정말 기쁩니다!
러스트는 누구든 믿음직하고 효과적인 소프트웨어를 만들 수 있게 도와주는 끝내주는 언어입니다.
만약 rustup을 통해서 Rust의 이전버전을 설치해놓은 상대라면, 업데이트는 아주 쉽습니다. 그냥 이렇게 치면 돼요.
rustup update stable
rustup을 설치한 적이 없다면, 우리 웹사이트의 설치 페이지에서 받을 수 있습니다. 그리고 깃허브에서 이번 버전에 대한 릴리즈 노트를 참조해보세요.
1.51.0엔 무엇이 있나요?
이번 릴리즈에는 Rust와 Cargo에 대해 상당히 큰 추가사항이 있습니다. 특히 const generic의 안정화와 Cargo의 새 기능 resolver가 가장 핫하죠.
저 멋진 세상으로 떠나봅시다!
Const Generics MVP
이 릴리즈 이전에도 Rust는 라이프타임이나 타입에 대해 파라미터화된 타입을 가질 수는 있었습니다.
예를 들어 배열의 요소 타입에 대해 generic한 구조체를 원한다면 다음과 같이 작성할 겁니다.
struct FixedArray
** // ^^^ 타입 제너릭 정의하고
** list: [T; 32] **
** **** // ^ 여기서 사용******
**} **
그러고 FixedArray
**struct FixedArray
** list: [u8; 32] **
**} **
이건 런타임 오버헤드 없이 재사용 가능한 코드를 만들 수 있는 강력한 기능이죠.
하지만 이번 릴리즈 전까지는 이러한 타입의 값을 쉽게 일반화할 수 없었습니다.
길이를 포함하는 배열 타입([T; N])이 특히 문제죠. 길이는 일반화할 수가 없었어요.
하지만 이제 1.51.0을 사용하면 integer, bool 또는 char 타입의 값에 대해 일반적인 코드를 작성할 수 있게 됐어요!
struct나 enum은 아직 unstable입니다.
이 변경 덕분에 이제 타입과 길이에 일반적인 배열 구조체를 가질 수 있게 됐죠.
다음 예시를 한번 보고 어떻게 쓰는지 익혀봅시다.
**struct Array<T, const LENGTH: usize> { **
** // ^^^^^^^^^^^^^^^^^^^ Const generic definition. **
** list: [T; LENGTH] **
** ****// ^^^^^^ 여기서 씀 ******
**} **
이러고 Array<u8, 32>을 호출한다면, 컴파일러는 다음과 같이 구체화 버전을 생성할 겁니다.
**struct Array<u8, 32> { **
** list: [u8; 32] **
**} **
Const generics은 새롭고 강력하며, 컴파일타임 안전한 API를 만듦에 있어 중요한 새 도구를 제공합니다.
const 제네릭에 대해 자세히 알아 보려면 "Const Generics MVP Hits Beta" 블로그 게시물에서 기능 및 현재 제한사항에 대한 자세한 내용을 확인할 수 있습니다.
여러분이 이걸로 얼마나 끝내주는 라이브러리를 만들어줄지 너무 기대되네요!
array::IntoIter Stable화
const generic 안정화의 일환으로 이를 사용하는 새 API인 std::array::IntoIter도 안정화됐습니다.
IntoIter를 사용하면 모든 배열에 대해 값별 반복자를 만들 수 있습니다.
이전에는 배열의 소유(owned) 값을 반복하는 편한 방법이 없었고 참조만 있었거든요.
**fn main() { **
** let array = [1, 2, 3, 4, 5]; **
** ****// 이전 ******
** for item in array.iter().copied() { **
** println!("{}", item); **
** } **
** ****// 지금 ******
** for item in std::array::IntoIter::new(array) { **
** println!("{}", item); **
** } **
**} **
현재 약간의 파손?(breakage)이 발생해서 배열에서 .into_iter() 메서드를 쓰는 대신 별도의 정적 메소드를 추가했습니다.
현재 .into_iter()는 참조 별 슬라이스 반복자를 나타냅니다.
우리는 향후 이걸 보다 쓰기 쉽게 만드는 방법을 찾아보고 있습니다.
Cargo의 새 기능 Resolver
종속성 관리는 참 어려운 문제죠. 개중에서도 가장 어려운 부분 중 하나는 두 개의 서로 다른 패키지에 의존할 때 사용할 종속성 버전을 선택하는 것입니다.
여기에는 버전 번호뿐만 아니라 패키지에 대해 활성화되거나 활성화되지 않은 기능도 포함됩니다.
Cargo의 기본 동작은 종속성 그래프에서 여러 번 참조될 때 단일 패키지의 기능을 병합하는 것입니다.
예를 들어 기능 A와 B에 foo라는 종속성이 있다고 칩시다.
이 둘이 패키지 bar와 baz에서 사용돼서 bar가 foo+A에 종속되고 baz가 foo+B에 종속되고요.
그럼 Cargo는 두 기능을 모두 병합하고 foo를 foo + AB로 컴파일합니다.
이는 foo를 한 번만 컴파일하면 된다는 이점이 있으며, bar와 baz 모두에서 재사용할 수 있다는 점이 있습니다.
근데 이것도 단점이 있습니다. 만약 빌드 종속성에서 활성화된 기능이 빌드하려는 타겟과 호환되지 않는 경우는 어떻게 될까요?
이에 대한 생태계에서의 일반적인 예시는, 많은 #![no_std] 크레이트에 포함된 선택적 std 기능입니다. 표준을 사용 가능할 때 추가 기능을 제공할 수 있도록 하죠.
이제 #![no_std] 바이너리에서 foo의 #![no_std] 버전을 사용하고 build.rs에서 빌드시 foo를 사용한다고 가정합시다.
빌드 타임 종속성이 foo+std에 의존하는 경우, 바이너리도 foo+std에 의존합니다.
타겟 플랫폼에서 std를 사용할 수 없기 때문에 더 이상 컴파일되지 않습니다.
이건 cargo의 오랜 문제였으며 이번 릴리즈에서는 Cargo.toml에 새로운 resolver 옵션이 추가되었습니다.
여기서 resolver = "2"를 설정하여 cargo에 새로운 resolver 기능을 시도하도록 지시할 수 있습니다.
동작에 대한 자세한 설명은 RFC 2957을 확인하시면 되는데요. 다음과 같이 요약할 수 있습니다.
Dev dependencies ->
패키지가 일반 종속성 및 개발 종속성으로 공유되면 현재 빌드에 개발 종속성이 포함된 경우에만 개발 종속성 기능이 활성화됩니다.
Host Dependencies ->
패키지가 일반 종속성 및 빌드 종속성, proc-macro로 공유되는 경우 일반 종속성의 기능은 빌드 종속성이나 proc-macro와 독립적으로 유지됩니다.
Target dependencies ->
패키지가 빌드 그래프에 여러번 표시되고 이러한 인스턴스 중 하나가 타겟별 종속성의 기능인 경우, 타겟별 종속성의 기능은 타겟이 현재 빌드중인 경우에만 활성화됩니다.
이로 인해 일부 cargo가 두 번 이상 컴파일될 수 있지만, 훨씬 더 직관적인 개발 경험을 가질 수 있겠습니다.
더 자세히 알고 싶다면 Cargo Book의 'Feature Resolver' 섹션에서 자세한 정보를 읽어보세요.
새로운 resolver를 설계하고 구현하는 데 노력을 쏟은 cargo 팀과, 관련된 모든 분들께 감사드립니다!
**[package] **
**resolver = "2" **
**# Or if you're using a workspace ******
**[workspace] **
**resolver = "2" **
분할된 디버그 정보
릴리즈에서 자주 강조되지는 않지만 Rust 팀은 컴파일 시간을 개선하기 위해 지속적으로 노력하고 있으며,이번 릴리즈는 macOS 버전에서 큰 개선점들이 있습니다.
알다시피 디버그 정보는 바이너리 코드를 소스 코드에 다시 매핑하는 것이니, 프로그램이 런타임에 무엇이 잘못되었는지에 대한 자세한 정보를 제공할 수 있는 거죠.
macOS에서 디버그 정보는 이전에 dsymutil이라는 도구를 사용하여 단일 .dSYM 폴더로 수집되었습니다.
이 도구는 시간이 걸리고 상당한 디스크 공간을 사용할 수 있습니다.
모든 debuginfo를 이 디렉토리에 수집하면 특히 바이너리가 이동중인 경우 런타임에 정보를 찾는데 도움이 됩니다.
하지만 프로그램을 약간만 변경하더라도 dsymutil은 최종 .dSYM 폴더를 생성하기 위해 전체 최종 바이너리를 실행해야한다는 단점이 있습니다.
모든 종속성이 항상 다시 수집되기 때문에 대규모 프로젝트일 경우 빌드 시간에 많은 시간이 추가될 수 있죠.
macOS에서는 Rust의 표준 라이브러리 없이 디버그 정보를 로드하는 방법을 몰랐기 때문에 이것은 필요한 단계였습니다.
최근 Rust backtrace은 dsymutil을 실행할 필요없이 디버그 정보 로드를 지원하는 다른 백엔드를 사용하도록 전환했으며, dsymutil 실행을 건너뛰는 지원을 안정화했습니다.
이제 debuginfo를 포함하는 빌드 속도를 크게 높이고 사용되는 디스크 공간의 양을 크게 줄일 수 있습니다.
광범위한 벤치마크를 실행하지는 않았지만 이러한 동작 덕분에 macOS에서 훨씬 빨라진 빌드에 대한 많은 보고가 있었죠.
rustc를 실행할 때 -Csplit-debuginfo = unpacked 플래그를 설정하거나 split-debuginfo [profile] 옵션을 Cargo에서 unpacked로 설정하여 이 새로운 동작을 설정할 수 있습니다.
"unpacked"옵션은 rustc에게 .o 개체 파일을 삭제하는 대신 빌드 출력 디렉토리에 남겨 두도록 지시하고 dsymutil 실행 단계를 건너뜁니다.
Rust의 backtrace 지원은 이러한 .o 파일을 찾는 방법을 알만큼 똑똑합니다.
lldb와 같은 도구도 이를 수행하는 방법을 알고 있습니다.
이것은 디버그 정보를 유지하면서 바이너리를 다른 위치로 이동할 필요가 없다면 잘 작동할 겁니다.
**[profile.dev] **
**split-debuginfo = "unpacked" **
Stable이 된 API들
전체적으로 이번 릴리즈에서는 슬라이스 및 peek 가능과 같은 다양한 타입에 대한 18개의 새로운 메서드가 안정화되었습니다.
주목할만한 추가 사항 중 하나는 ptr::addr_of!와 ptr::addr_of_mut!의 안정화입니다.
정렬되지 않은(unaligned) 필드에 대한 raw 포인터를 만들 수 있습니다.
이전에는 Rust가 정렬되고 초기화된 데이터를 가리켜야 하는 & / & mut을 필요로 했고, * const _로 &addr을 사용하면 &addr이 정렬돼야하므로 정의되지 않은 동작이 발생해 처리가 불가능했습니다.
이제 이 두 매크로를 사용해서 정렬되지 않은 포인터를 안전하게 만들 수 있습니다.
**use std::ptr; **
**#[repr(packed)] **
**struct Packed { **
** f1: u8, **
** f2: u16, **
**} **
let packed = Packed {
** f1: 1, **
** f2: 2 **
**}; **
// &packed.f2는 정렬되지 않은 참조를 생성하므로 undefined behavior가 됩니다!****
**let raw_f2 = ptr::addr_of!(packed.f2); assert_eq!(unsafe { raw_f2.read_unaligned() }, 2); **
그리고 다음 메서드들이 stable이 되었습니다.
Arc::decrement_strong_count
Arc::increment_strong_count
Once::call_once_force
Peekable::next_if_eq
Peekable::next_if
Seek::stream_position
array::IntoIter
panic::panic_any
ptr::addr_of!
ptr::addr_of_mut!
slice::fill_with
slice::split_inclusive_mut
slice::split_inclusive
slice::strip_prefix
slice::strip_suffix
str::split_inclusive
sync::OnceState
task::Wake
기타 변경점
1.51 릴리즈에는 이외의 기타 변경점들도 있습니다. Rust, Cargo, Clippy에서 무엇이 바뀌었는지 확인해보세요.
1.51.0의 컨트리뷰터들
1.51의 완성엔 수많은 사람들이 함께했습니다. 전부 여러분이 없었다면 불가능했을 거에요.
고마워요!