[Rust] 1.45.0 업데이트 발표 (번역)
원문
https://blog.rust-lang.org/2020/07/16/Rust-1.45.0.html
우리 러스트 팀은 새 버전 [1.45.0]을 발표하게 돼서 정말 기쁩니다!
러스트는 누구든 믿음직하고 효과적인 소프트웨어를 만들 수 있게 도와주는 끝내주는 언어입니다.
만약 rustup을 통해서 Rust의 이전버전을 설치해놓은 상대라면, 업데이트는 아주 쉽습니다. 그냥 이렇게 치면 돼요.
rustup update stable
rustup을 설치한 적이 없다면, 우리 웹사이트의 설치 페이지에서 받을 수 있습니다. 그리고 깃허브에서 이번 버전에 대한 릴리즈 노트를 참조해보세요.
1.45.0 stable엔 무엇이 있나요?
1.45엔 큰 2개의 변경점이 있습니다.
정수와 실수 간 캐스팅 시 발생했던 일부 long-standing unsoundness가 수정되었고요.
유명한 웹 프레임워크 중 하나에 필요한 마지막 기능이 stable이 되었단 겁니다.
**캐스팅 시 unsoundness 수정 **
Issue 10184는 2013년에 처음 열린 이슈였습니다. 1.0이 되기 1년하고도 반년 전이죠
여러분도 알듯이, rustc는 LLVM을 컴파일러 백엔드로 사용합니다.
그래서 아래와 같은 코드를 작성하면
**pub fn cast(x: f32) -> u8 **
**{ **
** x as u8 **
**} **
1.44 이전의 Rust 컴파일러는 아래와 같이 LLVM-IR를 산출했습니다.
**define i8 @_ZN10playground4cast17h1bdf307357423fcfE(float %x) **
**unnamed_addr #0 **
**{ **
**start: **
** %0 = fptoui float %x to i8 **
** ret i8 %0 **
**} **
fptoui은 캐스팅을 구현하는데요. 이건 "floating point to unsigned integer"의 축약입니다.
근데 여기엔 문제가 좀 있었습니다.
문서를 보면
ㅡㅡㅡㅡㅡㅡ
fptoui 인스트럭션은 부동소수점 피연산자를 가장 가까운, 그러니까 0을 향해 반올림(rounding)한 부호없는 정수 값으로 변환합니다.
만약 해당 값이 ty2에 들어맞지 않는다면, 그 결과는 포이즌 값(poison value)이 됩니다.
ㅡㅡㅡㅡㅡㅡ
라고 합니다.
이건 여러분이 컴파일러를 깊숙히 익혀본 적이 없다면 뭔지 이해하기 어려울 수 있어요.
쉽게 말해, 큰 부동소수점 수를 보다 작은 정수로 변환하면, 정의되지 않은 행동을 보인다는 겁니다.
예를 들어, 아래의 코드는 제대로 된 정의(well-defined)가 아닙니다.
**fn cast(x: f32) -> u8 **
**{ **
** x as u8 **
**} **
**fn main() **
{ **
** let f = 300.0;
** let x = cast(f); **
** println!("x: {}", x); **
**} **
이건 1.44에서, 제 컴퓨터에선 "x: 0"를 출력했습니다.
하지만 이건 뭐든 출력할 수 있고, 무슨 일이든 벌일 수 있습니다.
정의되지 않은 행동(undefined behavior)이죠.
그런데도 여기엔 unsafe 코드가 없습니다.
이런걸 우리는 소리없는(soundness) 버그라고 부릅니다. 컴파일러가 나쁜짓을 해서 버그를 만들죠.
그래서 우린 이슈 트래커에 이 버그들에 대해 I-unsound 태그를 달고 아주 진지하게 다뤄왔습니다.
이 버그는 해결에 오랜 시간이 걸렸죠.
올바른 방향이 무엇인지가 매우 불분명했기 때문입니다.
그래도 마침내, 우리는 이런 결정을 내렸습니다.
"포화(saturating) 캐스팅"을 수행하게 한 거에요.
새로운 unsafe 캐스팅이 추가된 거죠. 원한다면 체크를 스킵할 수도 있어요.
이건 배열 접근과 매우 유사합니다.
예를 들어 array[i]는 배열이 최소한 i+1 요소를 가진다는 걸 확실하게 체크하게 할 겁니다.
그래서 체크를 스킵하고자 한다면 **unsafe { array.get_unchecked(i) } **로 쓸수 있었어요.
그래서, 포화 캐스팅이 대체 뭔가요?
수정된 예제를 천천히 살펴봅시다.
**fn cast(x: f32) -> u8 **
**{ **
** x as u8 **
**} **
**fn main() **
**{ **
** let too_big = 300.0; **
** let too_small = -100.0; **
** let nan = f32::NAN; **
** println!("too_big_casted = {}", cast(too_big)); **
** println!("too_small_casted = {}", cast(too_small)); **
** println!("not_a_number_casted = {}", cast(nan)); **
**} **
이건 아래와 같은 텍스트를 출력합니다.
too_big_casted = 255
**too_small_casted = 0 **
**not_a_number_casted = 0 **
이젠 너무 큰 값이 들어오면 가능한 최대값을 반환합니다.
반대로 너무 작거나 NaN이 들어오면 가장 작은 값, 0을 산출합니다.
체크를 하지 않는 새로운 unsafe API를 사용하면 이전의 방식대로 쓸 수도 있습니다.
**let x: f32 = 1.0; **
**let y: u8 = unsafe { **
** x.to_int_unchecked() **
**}; **
하지만 항상 그랬듯이 이건 최후의 수단으로만 사용해야 합니다.
배열접근과 마찬가지로 컴파일러는 종종 체크에 대한 최적화를 수행할 수도 있습니다. 그래서 컴파일러가 판단할 수 있는 경우에는 safe와 unsafe 버전이 동일해요.
표현식, 패턴, 구문에 대한 function-like procedural 매크로
Rust 1.30.0에서는 아이템 위치에서의 function-like procedural 매크로가 stable이 되었습니다.
예를 들어 gnome-class crate를 보면
ㅡㅡㅡㅡㅡ
Gnome-class는 Rust의 procedural
매크로입니다. 매크로 내에서, 우린 가능한 만큼 Rust
y하게 미니멀한 언어를 정의할 수 있어요. 그리고 GObject
서브클래스나 그 프로퍼티, signal, 인터페이스 구현 등 GObject
의 기능들을 정의할 수 있게 해주는 확장을 가집니다.
unsafe를 요구하지 않는 게 목표입니다.
ㅡㅡㅡㅡㅡ
이건 이런식으로 쓸수있습니다.
**gobject_gen! **
**{ **
** class MyClass: GObject **
** { **
** foo: Cell
** bar: RefCell
** } **
** impl MyClass **
** { **
** virtual fn my_virtual_method(&self, x: i32) **
** { **
** ... do something with x ... **
** } **
** } **
**} **
"in item position"는 말이 약간 이상할 수 있는데요. 기본적으로 이건 코드의 특정 부분에서만 gobject_gen!할 수 있다는 걸 의미합니다.
그리고 1.45에서는 새로운 3가지 장소에서 procedural 매크로를 호출할 수 있는 기능이 추가되었습니다.
// "mac"이라는 이름의 procedural 매크로가 있다고 상상해봅시다. **
mac!(); // item position, 이건 이전에도 stable이었습니다. **
// 하지만 이제 3가지가 추가되었어요.
**fn main() **
**{ **
** let expr = mac!(); ****// 표현식 position **
** match expr **
** { **
** mac!() => {} ****// 패턴 position **
** } **
** mac!();**** // 구문 position **
**} **
더 많은 위치에서 매크로 사용이 가능해졌단 것은 꽤 흥미로운 점이죠.
하지만 많은 러스트 사용자들이 이 기능을 오랫동안 기다려온 이유가 있는데요. 바로 Rocket입니다.
2016년 최초 릴리즈 당시, Rocket은 Rust 환경에서 가장 훌륭하고 유명했던 웹 프레임워크였습니다.
아래는 rocket을 사용한 새로운 헬로월드 예제입니다.
**#[macro_use] extern crate rocket; **
**#[get("/
**fn hello(name: String, age: u8) -> String **
**{ **
** format!("Hello, {} year old named {}!", age, name) **
**} **
**#[launch] fn rocket() -> rocket::Rocket **
**{ **
** rocket::ignite().mount("/hello", routes![hello]) **
**} **
이제까지, 로켓은 nightly에만 있는 기능에 의존했습니다. 유연성과 사용성을 위해서요.
사실 프로젝트 홈페이지에서도 보이는데요. Rocket의 현재 버전에서 위의 예제는 proc_macro_hygiene 기능의 컴파일을 요구했습니다.
하지만, 여러분이 기능의 이름에서 추측했듯이, 오늘은 stable한 상태로 배송될 거에요!
이 이슈는 Rocket의 nightly-only 기능 히스토리를 트래킹했습니다. 이제 다 체크아웃하면 될겁니다!!
Rocket의 다음 버전은 아직 개발중에 있지만, 릴리즈되면 많은 사람들이 기뻐할거에요. :)
라이브러리 변경점
1.45에서는 다음 API들이 stable되었습니다.
Arc::as_ptr
BTreeMap::remove_entry
Rc::as_ptr
rc::Weak::as_ptr
rc::Weak::from_raw
rc::Weak::into_raw
str::strip_prefix
str::strip_suffix
sync::Weak::as_ptr
sync::Weak::from_raw
sync::Weak::into_raw
char::UNICODE_VERSION
Span::resolved_at
Span::located_at
Span::mixed_site
unix::process::CommandExt::arg0
그리고 이제는 코드상에서 iterate를 위한 char ranges를 사용할 수 있습니다.
**for ch in 'a'..='z' **
**{ **
** print!("{}", ch); **
**} **
**println!(); **
// "abcdefghijklmnopqrstuvwxyz" 출력
변경점의 전체 목록을 보고 싶다면, 전체 릴리즈 노트를 참조하세요.
기타 변경점
이것들 외에도 여러가지 변경점이 있는데요.
Rust와 Cargo, Clippy에서 무엇이 바뀌었는지 확인해보세요.
1.45.0의 컨트리뷰터들
1.45의 완성엔 수많은 사람들이 함께했습니다. 전부 여러분이 없었다면 불가능했을 거에요.
고마워요!