서문

이 문서는 Rust 언어의 에디션 가이드입니다! "에디션"은 Rust에 찾아오는 큰 변화들을 나타내는 말입니다.

이 가이드에서 우리는 다음과 같은 것들에 대해서 이야기 할 것입니다:

  • "에디션"이란 무엇인가요
  • 각각의 에디션에 대해서
  • 새로운 에디션으로 코드를 어떻게 이식하나요

참고로 Rust 표준 라이브러리는 각각의 Rust Release에 맞춰서 개발됩니다. 이 가이드에서 우리는 굵직하고 중요한 신규요소들에 대해서만 다룰건데요, 새롭게 추가되었지만 이 가이드에서 다루어지지 않은 수없이 많은 기능들이 있으니 꼭 찾아보세요! 관심있으시다면 표준 라이브러리 문서를 참고하시면 됩니다.

한국어판을 읽으시는 분들께

이 문서는 서로 다른 러스트 에디션들간의 간극을 효과적으로 메우기 위해 만들어진 러스트 에디션 가이드의 한국어판입니다. 혹시 여러분이 러스트 언어를 처음 접하시는 거라면 공식 러스트 언어 책을 먼저 읽어보시길 권합니다. 이 가이드는 이미 러스트를 쓰고 계시는 분들께 러스트의 새로운 에디션에 추가된 신규사항들을 쉽고 빠르게 보여드리기 위해서 만들어졌습니다.

아래는 제가 Rust 에디션 가이드를 번역하면서 내린 결정들입니다. 읽으면서 참고하시면 될 것 같습니다.

  1. 1대1 직역보다는 동일한 의미를 가진 의역 사용

초기에는 1대1 직역을 고집하려 하였으나, 번역 작업을 하다보니 직역을 하게 되면 문체가 너무 영어 번역체같아져버려서 가독성을 해치는 것을 발견하였습니다. 최대한 원문의 의미를 그대로 유지한 채로 능력껏 의역을 하였고, 혹시 읽으시는 도중에 오역을 발견하신다면 yyang542@wisc.edu 또는 yegeunyang@gmail.com으로 연락 주시면 감사드리겠습니다.

  1. 모든 단어를 100%한국어로 번역하는 것이 아닌, 단어별 취사선택

특정한 단어들은 영어 단어를 그대로 쓰는 것이 더 낫다고 생각합니다. 예를 들어, backward compatibility, idiomatic code, nightly, struct, enum, trait object 등등입니다. 번역에 있어서 일관성을 유지하기 위해서 이런 단어들은 표를 만들어서 아래와 같이 관리하고 있습니다.

영어단어
Rust 러스트 프로그래밍 언어
Release 주기적으로 발표되는 러스트의 신규버전
Backward compatibility 과거의 코드가 계속 잘 동작되도록 보장하는것
crate 러스트 프로젝트
dependency 의존성 라이브러리
stable 러스트 안정 버전
beta 러스트 베타 버전
nightly 러스트 개발 버전
Cargo 러스트 패키지 관리 툴
idiomatic code 잘 쓰인 러스트 코드

에디션이란 무엇인가요?

Rust의 새로운 Release는 6주단위로 찾아옵니다. 이 말은 유저들이 주기적으로 새로운 기능들을 받아보게 된다는 뜻이죠. 이 주기는 다른 여타 프로그래밍 언어들과 비교해봤을때 굉장히 짧기때문에, 각각의 수정사항들은 아무래도 그 수가 적을수밖에 없습니다. 그렇지만, 꽤 오랜 시간이 흐르면, 이런 아주 작은 수정사항들이 모여서 굉장히 큰 변화가 됩니다. 문제는 Release만으로 이러한 것들을 표현하기에는 아무래도 큰 혼란이 찾아올 수 있다는 것입니다. 예를 들면.. 엥? Rust 1.10 과 Rust 1.20 는 완전히 다르잖아?? 같은 상황이 벌어질 수 있죠.

매 2년 또는 3년을 주기로, 우리는 새로운 에디션을 소개할 것입니다. 각각의 에디션은 새로운 기능들을 깔끔한 패키지와 완전히 업데이트된 공식문서 그리고 그에 따른 Tool들과 함께 가져올 것입니다.

이것은 다양한 사람들의 다양한 요구를 충족시켜 주는데요,

활발히 Rust를 사용하는 유저들에게는 이 에디션이 Rust의 점진적인 변화들을 쉽게 이해할 수 있는 기회가 될 것이고요,

Rust를 현재 사용하고 있지 않은 사람들에게는 Rust에 중대한 변화가 찾아왔다는 것을 알고 한번 들여다보는 좋은 기회가 될 것입니다.

또한 Rust를 직접 개발하고 있는 개발자들에게는 각각의 에디션이 Rust개발에 있어서 중요한 포인트가 될 것입니다.

호환성, Compatibility

매번 새로운 에디션이 찾아올때마다 Crate들은 그것에 맞춰서 적응을 해야 합니다. 그래야 새로운 에디션이 과거 에디션과 호환되지 않는 변화를 포함하거나, 과거 에디션에서 이미 변수 이름등으로 사용되고 있을지도 모르는 새로운 키워드를 추가하는 일, 그리고 warning을 error로 만드는 일 등을 할 수 있기 때문이죠. Rust 컴파일러는 그 전에 나온 모든 Rust 에디션들을 지원할 것이고, 여러분은 여러분의 crate를 원하는 에디션과 함께 사용할 수 잇습니다. 에디션간의 변화는 Rust 컴파일러가 코드를 파싱하는 방식에만 영향을 끼칩니다. 따라서, 여러분이 Rust 2015 에디션을 사용하고 있으시고, dependency들중 하나가 Rust 2018에디션을 사용하고 있더라도 아무런 문제가 발생하지 않습니다. 그 반대의 경우도 마찬가지이고요.

아 그리고, Rust의 대부분의 기능들은 Rust의 어떤 에디션을 쓰던지 공통적으로 사용가능합니다. 사용하고 계시는 에디션에 상관없이, 여러분은 새로운 stable release가 찾아오는 것과 함께 개선점을 보게 될 것입니다. 다만, 몇몇 경우에는, 특히 새로운 키워드의 추가, 그리고 기타 다른 특수한 이유들로, 오직 최신 에디션에서만 쓸 수 있는 새로운 추가사항들이 있을 수 있습니다. 여러분이 이러한 새로운 기능들을 이용하고 싶으시다면 새로운 에디션으로 업그레이드 하는 것을 고려해 보시길 바랍니다.

어떻게 2018에디션을 체험해 보나요?

현재 이 글을 쓰고 있는 시점에서는 Rust에는 두개의 에디션이 있습니다. 2015 에디션과 2018에디션이 바로 그것이죠. 2015 버전은 여러분이 현재 쓰고 있는 바로 그 Rust 이고요, Rust 2018에디션은 현재 베타버전입니다. 이 새로운 에디션은 Rust 1.31 과 함께 2018년 12월 6일에 Stable로 넘어올 것입니다.

그 전에 2018 에디션을 체험해 보고 싶으시다면 베타 툴체인을 설치해 주세요!

> rustup install beta

Rust에 찾아올 모든 새로운 기능들을 미리 체험해 보고 싶으시다면, nightly를 이용하시면 됩니다.

> rustup install nightly

여러분이 cargo fix같은 명령어를 이 가이드의 다른 부분에서 보게된다면, 아마 여러분은 그 앞에 툴체인을 명시해 줘야할 것입니다.

> cargo +beta fix --edition
> cargo +nightly fix --edition

새로운 프로젝트 만들기

여러분이 Cargo로 새로운 프로젝트를 만들려고 한다면, Cargo가 자동으로 최신 에디션을 위한 설정을 추가할 것입니다.

> cargo +nightly new foo
     Created binary (application) `foo` project
> cat .\foo\Cargo.toml
[package]
name = "foo"
version = "0.1.0"
authors = ["your name <you@example.com>"]
edition = "2018"

[dependencies]

저기 있는 edition = 2018 설정만 하면 여러분의 패키지가 Rust 2018 에디션을 쓸 수 있습니다!.

만약 여러분이 옛날 버전을 쓰고 싶다면 언제든지 이 값을 바꾸실 수 있습니다. 예를 들면,

[package]
name = "foo"
version = "0.1.0"
authors = ["your name <you@example.com>"]
edition = "2015"

[dependencies]

이렇게 하시면 여러분의 패키지가 Rust 2015로 빌드될 것입니다.

기존 프로젝트를 새로운 에디션으로 전환시키기

새로운 에디션들은 아마 여러분이 Rust 코드를 짜는 방식을 변화시킬 것입니다. - 새로운 에디션들은 새로운 문법, 언어 요소, 라이브러리와 함께 찾아오고, 몇몇 기능들은 삭제되기도 하기 때문이죠. 예를들면, try, async, 그리고 await등등의 키워드들이 Rust 2018에디션에 추가될 예정입니다. 그렇지만 이 키워드들은 Rust 2015 에디션에는 들어있지 않죠. 만약 여러분의 프로젝트가 Rust 2015를 사용하고 있고 여러분이 이제부터 Rust 2018에디션을 사용하자고 하신다면, 아래와 같이 하시면 됩니다!

우리는 새로운 에디션으로의 전환이 최대한 쉽도록 노력하고 있습니다. 우리는 Rust 2018로 업그레이드하는 것이 여러분에게 어렵게 느껴진다면, 그것이 버그때문일지도 모른다고 생각합니다. 만약 여러분이 업그레이드를 하는 과정 중에 문제가 발생한다면 버그 신고, 여기에 신고해 주세요!.

여기 예시가 있습니다. 우리가 이 trait을 가지고 있다고 해 봅시다. src/lib.rs:


# #![allow(unused_variables)]
#fn main() {
trait Foo {
    fn foo(&self, Box<Foo>);
}
#}

이 코드는 익명 parameter를 사용합니다. 바로 Box<Foo>이죠. 이 기능은 Rust 2018에서는 지원되지 않습니다 따라서, Rust 2018에디션에서는 컴파일 에러를 발생시킬 것입니다. 여러분이 Rust 2018을 사용하고 싶으시다면 이런 코드들을 빨리 업데이트하셔야겠죠?

여러분의 코드를 새로운 에디션에 맞춰서 업데이트시키세요

여러분의 코드는 새로운 에디션과 호환되지 않는 기능들을 사용하고 있을수도, 사용하고 있지 않을수도 있습니다. Rust 2018로의 전환을 돕기 위해서 우리는 Cargo에 새로운 Subcommand를 추가하였습니다. 아래에 있는 명령을 실행시켜보시죠.

> cargo fix --edition

이 명령어는 여러분의 코드를 체크해보고 자동적으로 모든 이슈들을 수정할것입니다 (할수 있는 범위 내에서요) 이 명령어를 실행하고 난 다음, src/lib.rs에 있는 코드가 어떻게 바뀌어 있나 봐볼까요?


# #![allow(unused_variables)]
#fn main() {
trait Foo {
    fn foo(&self, _: Box<Foo>);
}
#}

이 명령어가 이 Trait object의 parameter name을 위해서 코드를 수정했네요! 이 경우에는, 그 전의 이름이 없는 관계로, cargo fix가 이름을 _로 대체했습니다. (사용되지 않는 변수이름들을 나타내기 위한 표현이죠)

cargo fix는 등장한지 얼마되지 않았기 때문에, 여러분의 모든 코드를 자동적으로 고쳐줄수 있는 단계에 있지는 않습니다. 만약 cargo fix가 고치지 못하는 무언가가 있다면, 그에 맞는 warning이 출력될 것입니다. 만약 여러분이 이러한 warning들을 보게 된다면, 여러분이 직접 그 코드들을 수정해야 합니다. 이 가이드에는 이를 위한 섹션이 있으니 그 부분을 읽어보시길 바랍니다. 또한, 만약 여러분이 이와 관련된 문제를 겪고 있다면 유저 포럼에 가보시는 것도 고려해 보세요.

여러분이 그 어떠한 warning도 받지 않을 때까지 cargo fix --edition을 계속 실행해 보세요.

그러면.. 축하드립니다! 여러분의 코드는 이제 Rust 2018과 Rust2015 모두에서 사용가능합니다.

새로운 기능들을 사용하기 위해서 새로운 에디션 사용하기

여러분이 새로운 기능들을 사용하고 싶으시다면, 명시적으로 그것을 밝혀줘야만 합니다. 여러분이 준비가 됐다 싶으시면, 새로운 에디션을 사용하기 위해서 Cargo.toml파일을 수정하세요. 예를 들면,

[package]
name = "foo"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
edition = "2018"

만약에 edition키가 파일 안에 없다면, Cargo는 자동적으로 Rust 2015 에디션을 사용할것입니다. 그렇지만, 이 경우에는 우리가 2018에디션을 사용하겠다고 명시적으로 밝혔기 때문에, 우리의 코드는 Rust 2018 에디션으로 컴파일될 것입니다!

새로운 에디션에서 idiomatic한 코드 짜기

에디션들은 단순히 새로운 기능을 추가하는 것과 오래된 기능들을 제거하는것들에만 관한것이 아닙니다. 어떠한 프로그래밍 언어에서든 idiomatic한 코드는 시간에 따라 변화하죠. 또한 Rust라고 그에 대한 예외일 수는 없습니다. 오래된 idiomatic 코드들이 계속 컴파일 되긴 하겠지만, 그것이 새로운 에디션에서도 idomatic한 코드일것이란 보장은 없습니다.

여기 이제는 구식이 되어버린 옛날의 idiomatic한 코드가 있네요. (어디서 본것 같죠?)


# #![allow(unused_variables)]
#fn main() {
trait Foo {
    fn foo(&self, _: Box<Foo>);
}
#}

Rust 2018 에디션에서는 trait object에 dyn 키워드를 쓰는 코드가 idiomatic한 코드입니다.

최종적으로는 우리는 cargo fix가 이러한 모든 idiom들을 자동적으로 업그레이드 하게 되는 것을 바라고 있습니다. 그렇지만, 지금 현재로서는 "idiom lints"가 아직 자동화되지 않았습니다. Rust 컴파일러는 cargo fix와 연동되어서 수정 제안점들을 제시해야하지만 그러지 못하고 있죠. 몇몇 경우에서는 오히려 잘못된 제안을 하기도 하고요. idiom lint를 사용하는 것은, 심지어 cargo fix와 함께 사용되더라도, 아마도 여러분의 crate가 작동하지 못하도록 만들어버리거 또는 전혀 아무런 것도 고치지 못한채 그대로 내버려 둘 수 있습니다. 우리는 이러한 idiom들을 업그레이드 하는 것 뿐만 아니라 전체적인 Rust 2018로의 전환이 매우 쉽게 이루어지도록 하기 위한 작업중에 있습니다만 아직 그 작업이 완료되지는 않았습니다. 따라서 여러분이 컴파일러/Cargo 버그들과 맞닥뜨리고 그것을 수정할 수 있으신 경우에만 아래의 명령을 실행시키세요.

$ cargo fix --edition-idioms

명령을 실행시키고 나면, src/lib.rs에 있는 코드는 다음과 같이 보일 것입니다.


# #![allow(unused_variables)]
#fn main() {
trait Foo {
    fn foo(&self, _: Box<dyn Foo>);
}
#}

훨신 idiomatic하네요! 또한 직접 코드를 수정해야하는 수고도 덜었고요.

그리고 cargo fix가 여전히 여러분의 모든 코드들을 업데이트하지 못할 수 있다는 것을 알아두세요. 만약 cargo fix가 고치지 못하는 무언가가 있다면 단순히 그에 따른 warning을 콘솔 화면에 출력할 것이고 여러분은 그것들을 직접 고치시면 됩니다.

이 전에 언급했던대로, 아직 idiom lints에는 많은 버그들이 산재해있습니다. 아직 실제 사용되어질수 있을만한 수준에 이르지는 못했다는 뜻이죠. 여러분은 rustc가 제안한 수정 사항이 실제로 적용되고 그것이 컴파일 에러로 이어졌을때라면 언제든지 Cargo로부터 굉장히 무시무시한 warning을 받을 수 있는데요, 만약 어려분이 cargo fix로 하여금 할 수 있는 한에서는 최대한 수정을 하게 하고 싶으시다면, 아래의 명령어를 실행시키실수 있습니다. (그렇지만, 컴파일 에러를 일으킬수도 있어요!):

$ cargo fix --edition-idioms --broken-code

이 명령어는 cargo fix로 하여금 자동적으로 추천 수정사항들을 그것들이 실제로 정상작동하는지 아닌지 여부에 관계없이 적용하도록 합니다. 언제나처럼 여러분은 모든 수정사항들이 적용된 뒤에 컴파일 결과를 보게 될 것이고요, 만약 여러분이 무언가 이상하거나 잘못된 것을 발견하신다면, 주저하지 마시고 Cargo team에게 신고를 해주세요. 우리는 신고받은 것들을 검토하고 고칠것입니다.

부디 여러분이 Rust의 새로운 에디션을 즐기시길 바랍니다!

Rust 2015

Rust 2015 에디션은 "안정성"에 집중했습니다. Rust release 1.0과 함께 찾아온 이 버전은 현재 Rust의 디폴트 에디션입니다. 에디션 시스템은 2017년 말쯤에 구상되어졌고, Rust 1.0 Release는 2015년 5월쯤이었습니다. 따라서, 이 버전은 여러분이 특정한 에디션을 쓰겠다라고 하지 않는 이상 기본 에디션이 될 것입니다. (Backward compatability 때문이죠.)

Rust 1.0 버전이 안정성에 초점을 맞췄던 이유는 이 버전이 Rust 개발에 매우 큰 변화를 가져왔기 때문입니다. 이 버전 이전의 Rust는 안정적이지 못하고 시시각각 변화하고 있었죠. 이것은 Rust로 거대한 소프트웨어를 짜는 것을 매우 어렵게했고, 또한 사람들이 Rust를 배우는 것 또한 어렵게 하였습니다. Rust 1.0 과 Rust 2015 가 등장함으로써, 우리는 기존에 쓰여진 코드들이 계속 동작할 수 있도록 확실히 보장을 하면서 동시에 사람들이 Rust로 어떠한 프로젝트라도 주도할 수 있도록 하는 확고한 토대를 세웟습니다.

이 버전이 현재 Rust의 기본 에디션이기 때문에, 여러분은 Rust 2015 버전으로 포팅을 할 필요가 없습니다. 여러분은 앞으로 이 버전에서 벗어나 새로운 버전으로 나아가게 될 것입니다. 따라서 이 버전에 대해서는 딱히 특별히 더 이야기 할 거리가 없네요!

Rust 2018

에디션 시스템은 Rust 2018의 release와 함께 만들어졌습니다. Rust 2018의 주안점은 생산성에 있습니다. Rust 2015에디션과 비교해서, Rust 2018 에디션은 새로운 기능들, 더 간단한 언어문법 (몇몇 경우에 한해서), 더 똑똑한 borrow-checker, 그리고 기타 수없이 많은 여러 가지를 가지고 있죠. 이러한 모든 것들은 생산성이라는 하나의 목표를 위해서 개발되어졌습니다. Rust 2015에디션이 기초를 확실히 다지는 것이었다면, Rust 2018은 그것을 정교하게 다듬는 것입니다. 예를 들면, 코드를 짜는 것을 더 간단하고 쉽게 만들기, 몇몇 일관되지 못한 요소들 제거하기와 같은 것들이죠.

모듈 시스템

이 챕터에서 우리는 모듈 시스템에 찾아온 몇가지 변화들에 대해서 이야기해 볼 것 입니다. 그 중 가장 주목할 만한 변화는 [더 명확해진 path]입니다.

키워드를 식별자로 사용하기

Minimum Rust version: beta

다른 많은 프로그래밍 언어들처럼 Rust에도 "키워드"라는 개념이 존재합니다. 키워드들은 언어 안에서 특별한 의미를 가집니다. 그리고 여러분은 이러한 키워드들을 변수이름, 함수 이름 등등으로 쓸 수 없죠. 그렇지만 이러한 키워드들을 일반적으로는 식별자로 쓸 수 없는 곳에서 식별자로 쓸 수 있게 만드는 방법이 존재합니다.

예를 들어보자면, match는 키워드입니다. 만약 여러분이 이 코드를 컴파일 하려고 한다면:

fn match(needle: &str, haystack: &str) -> bool {
    haystack.contains(needle)
}

컴파일 에러가 발생하겠죠:

error: expected identifier, found keyword `match`
 --> src/main.rs:4:4
  |
4 | fn match(needle: &str, haystack: &str) -> bool {
  |    ^^^^^ expected identifier, found keyword

그렇지만 여러분은 이렇게 할 수 있습니다:

fn r#match(needle: &str, haystack: &str) -> bool {
    haystack.contains(needle)
}

fn main() {
    assert!(r#match("foo", "foobar"));
}

이 방법을 쓸때, 함수의 정의부분에서뿐만 아니라 함수를 호출할때에도 r#를 써야하는 점에 주의하세요.

왜 이런 기능이 추가되었나요?

이 기능은 그다지 유용한 기능은 아닙니다만, 이 기능을 소개하게 된 주요한 동기를 말해보자면, 에디션간의 호환성문제때문입니다. 예를 들어서, try는 Rust 2015에디션에서는 키워드가 아니었습니다. 그렇지만 Rust 2018에디션에서 키워드가 돠었지요. 따라서 만약에 여러분이 Rust 2015에디션에서 try라는 이름을 가진 함수를 정의했고, 이 함수를 Rust 2018에디션에서 사용하고 싶으시다면 위의 기능을 이용하시면 됩니다.

새로운 키워드들

새롭게 2018에디션에 추가된 키워드들은 다음과 같습니다:

asyncawait

async키워드는 async fnasync ||클로저, 그리고 async { .. } 블록에서 사용되어지기 위해 예약되었습니다. 또한 await는 우리가 await!(expr)과 관련해서 보다 유연한 선택을 내릴 수 있도록 예약되었습니다. 더 자세한 정보를 원하신다면, RFC 2394를 참고하세요.

try

try키워드가 Rust 2018에디션에 새로이 추가되었기 때문에, do catch { .. } 블록은 이제 try { .. }라고 불리게 됬습니다. 더 자세한 정보를 원하신다면 RFC 2388를 참고하세요.

더 명확한 Path

Minimum Rust version: beta Minimum Rust version: nightly for "uniform paths"

Rust 초심자들에게 Rust의 모듈 시스템은 때때로 가장 어려운 장벽중의 하나입니다. 모든 사람들이 저마다 어려워 하는 것은 다 다르지만 Rust의 모듈 시스템이 모든 사람들에게 어려운 데에는 이유가 있습니다: 모듈 시스템을 정의하기 위한 규칙들은 일관성있으며 단순하지만, 그 규칙들의 순서가 Rust초심자들에게는 일관되지 않으며, 비직관적이고 때로는 알쏭달쏭하게 다가오기 때문입니다.

따라서, 우리는 Rust 2018에디션에서 모듈시스템에 추가된 몇가지 변경점들에 대해서 이야기 해 보려 합니다. 이러한 변경점들은 모듈 시스템을 단순화하고 명확하게 만들기 위해서 추가되었습니다.

간단히 요약하자면:

  • 더이상 extern crate를 쓸 필요가 없습니다!
  • crate 키워드는 현재 crate를 가리키는데 사용됩니다
  • 절대 경로는 crate 이름으로 시작합니다.
  • foo.rsfoo/ 하위디렉토리가 공존할 수도 있습니다; mod.rs는 서브디렉토리에 서브모듈을 만들때 더이상 필요하지 않습니다.

이러한 새 규칙들은 이렇게 보면 전혀다른 별개의 규칙들처럼 보일 수 있습니다. 그렇지만 이러한 규칙들을 만든 목적은 단 하나입니다. 모듈 시스템을 단순화하는 것이죠!

추가적으로, nightly 에서는 "uniform path"라고 불리는 또다른 변경점이 있습니다. 이 변경점은 새롭게 바뀐 Path와 backward compatible 합니다. 이 가이드 마지막에 "uniform section"은 이 가이드 마지막에 독자적인 섹션을 가지고 있으니 관심있으시면 찾아보시길 바랍니다

자세한 변경사항

자 이제 각각의 새로운 변경점들에 대해서 더 자세하게 이야기 해 봅시다.

더 이상 extern crate가 필요하지 않습니다

이 변경점은 굉장히 단순하면서도 명확합니다: 더이상 여러분의 프로젝트로 외부의 crate를 import 하기 위해서 extern crate를 적을 필요가 없습니다

기존 코드:

// Rust 2015

extern crate futures;

mod submodule {
    use futures::Future;
}

새로운 코드:

// Rust 2018

mod submodule {
    use futures::Future;
}

이제, 새로운 crate를 Cargo.toml파일에 추가하는 것만으로 그 crate를 여러분의 프로젝트에 손쉽게 추가할 수 있습니다. 만약 여러분이 Cargo를 사용하고 있지 않으시다면, 여러분은 기존에 하던 것과 마찬가지로 외부 crate의 위치를 지정해주기 위해 --extern 플래그를 rustc에 넘겨주던 것을 계속 하시면 됩니다

참고로: cargo fix는 현재 이 변화에 대해서 자동적으로 수정을 하지 않습니다. 우리는 아마도 미래에 이 기능을 추가할 것입니다.

extern crate는 매크로를 불러올때도 쓰였었죠. 이 역시 더이상 필요하지 않습니다. 매크로 섹션을 참고하세요.

만약에 여러분이 as 를 crate를 rename하기 위해 아래와 같이 사용해오셨다면:

extern crate futures as f;

use f::Future;

단순히 extern crate부분만을 제거한 코드는 동작하지 않을 것입니다. 다음과 같이 하세요:

use futures as f;

use self::f::Future;

이 변경점은 f를 사용하는 어떤 모듈에서든지 일어나야 합니다.

crate키워드는 현재 있는 crate를 나타냅니다.

crate root가 아닌 곳에서 use를 쓸때, 여러분은 root를 crate::와 같이 표현할수 있습니다. 예를 들어, crate::foo::bar는 crate안 어디에 있던지 언제나 crate의 foo모듈 안의 이름 bar를 나타낼것입니다.

앞에 붙이던 ::는 지금까지 crate root를 나타내거나 외부 crate를 나타내는데 쓰였었는데요, 이제부터는 모호함을 줄이고자 오직 외부 crate를 나타내는 데에만 사용될 예정입니다. 예를 들어서, ::foo::bar는 언제나 외부 crate foo안의 이름 bar를 나타낼 것입니다.

Path에 관한 변경사항들

Rust 2018에서는 use안에 쓰인 path들은 무조건 crate이름, crate, self 또는 super중의 하나로 시작해야 합니다.

예를 들어 Rust 2015에서 쓰인 이 코드를 보시죠:

// Rust 2015

extern crate futures;

use futures::Future;

mod foo {
    pub struct Bar;
}

use foo::Bar;

위 코드는 이제 이렇게 바뀔 겁니다:

// Rust 2018

// 'futures' is the name of a crate
use futures::Future;

mod foo {
    pub struct Bar;
}

// 'crate' means the current crate
use crate::foo::Bar;

여기에 더해서, 이 모든 path들은 use가 아닌 다른 곳에서 사용될때에도 똑같이 사용되어질수 있습니다. Rust 2015에서 쓰였던 아래 코드를 보시죠:

// Rust 2015

extern crate futures;

mod submodule {
    // this works!
    use futures::Future;

    // so why doesn't this work?
    fn my_poll() -> futures::Poll { ... }
}

fn main() {
    // this works
    let five = std::sync::Arc::new(5);
}

mod submodule {
    fn function() {
        // ... so why doesn't this work
        let five = std::sync::Arc::new(5);
    }
}

실제론 여러분이 mod submodule를 저렇게 반복할일은 없고 function을 그냥 첫번째 mod 블록에 정의하겠지만요 하하

이 예시에서 submodule안에 futures라고 이름 붙여진 것이 없기 때문에 my_poll의 function signature는 옳지 않습니다; 그 말은, 이 path가 relative path로 간주된다는 말이죠. use futures::는 심지어 futures::가 올바른 코드가 아니더라고 잘 동작합니다. std와 함께 쓰일 경우에는 더욱더 복잡해지죠. 왜냐면 extern crate std;라고 여러분이 쓰실 일은 없으니까요. 그래서 이 코드가 main에서는 잘 동작하지만 서브모듈에서는 동작하지 않는 이유가 대체 무엇일까요? 똑같은 문제입니다: 이 path가 use 선언 안에 있지 않기 때문에 relative path로 간주된다는 말이죠. extern crate std;는 crate root에 삽입됩니다. 그래서 main에서는 잘 동작하지만 서브모듈에서는 동작하지 않는 것이죠.

그렇다면 Rust 2018에서는 이 코드가 어떻게 보일까요?:

// Rust 2018

// no more `extern crate futures;`

mod submodule {
    // 'futures' is the name of a crate, so this works
    use futures::Future;

    // 'futures' is the name of a crate, so this works
    fn my_poll<T, E>() -> futures::Poll {
        unimplemented!()
    }

    fn function() {
        // 'std' is the name of a crate, so this works
        let five = std::sync::Arc::new(5);
    }
}

fn main() {
    // 'std' is the name of a crate, so this works
    let five = std::sync::Arc::new(5);
}

훨씬 더 간단하고 명확하네요!.

더 이상 mod.rs가 필요하지 않습니다

Rust 2015에서 여러분이 서브모듈을 만들고 싶으셨다면:

///  foo.rs 
///  or 
///  foo/mod.rs

mod foo;

그 서브모듈은 foo.rs안에 만들어지거나 foo/mod.rs안에 만들어질 수 있었죠. 만약 그 서브모듈도 또다른 서브모듈을 가지고 있었다면, 무조건 그 서브모듈은 foo/mod.rs안에 있어야만 했습니다. 따라서 foobar서브모듈은 foo/bar.rs안에 있겠죠. foo/bar.rs.

그렇지만 Rust 2018에서는 mod.rs가 더 이상 필요하지 않습니다.

///  foo.rs 
///  foo/bar.rs

mod foo;

/// in foo.rs
mod bar;

foo.rs는 이제 말그대로 foo.rs입니다. 그리고 그것의 서브모듈은 말그대로 foo/bar.rs이고요. 이 변경점은 쓸데없이 사용되던 mod.rs의 사용을 없애고 여러분이 여러분의 프로젝트를 파일 매니저를 통해서 열었을때, 수없이 많은 mod.rs를 보는 것이 아니라 한눈에 소스코드들만 볼 수 있도록 해줍니다.

Uniform paths

Uniform path는 현재 nightly에서만 사용가능합니다.

Rust 2018의 Uniform path는 path를 지정하는 방식을 하나로 통합하고 단순화하는 역할을 합니다. Rust 2015에서는 path들이 use로 선언될 때와 다른 곳에서 사용되어졌을때 상이하게 행동했었습니다. 정확히 말하자면, use안의 path들은 항상 crate root에서부터 시작되었고, 프로젝트 안의 다른 path들은 (암묵적으로) 현재 있는 모듈에서부터 시작했죠. 이러한 차이점은 root에 있는 모듈들입장에서는 전혀 문제가 되는 것이 아닙니다. 따라서 작은 프로젝트의 경우는 괜찮았지만 여러분이 서브모듈을 가지고 있는 큰 프로젝트들에서 일하게 되는 것은 문제가 되었죠.

Rust 2018에서의 uniform path는 use안의 path들이 다른 코드에서의 path들과 똑같은 방식으로 작동하도록 합니다. (top-level 모듈이던지 다른 서브모듈이던지 말이죠) 여러분은 언제나 현재 모듈을 기준으로 한 relative path를 쓰실 수 있고, 아니면 외부 crate 이름으로 시작하는 path, 또는 키워드 crate, super 또는 self 로 시작하는 path를 쓰실 수 있습니다.

예를 들면 Rust 2015의 이 코드는:

// Rust 2015

extern crate futures;

use futures::Future;

mod foo {
    pub struct Bar;
}

use foo::Bar;

fn my_poll() -> futures::Poll { ... }

enum SomeEnum {
    V1(usize),
    V2(String),
}

fn func() {
    let five = std::sync::Arc::new(5);
    use SomeEnum::*;
    match ... {
        V1(i) => { ... }
        V2(s) => { ... }
    }
}

Rust 2018에서도 똑같이 작동할 겁니다. 여러분이 extern crate라인을 제거할 수 있다는 것만 빼고요:

// Rust 2018 (uniform paths variant)

use futures::Future;

mod foo {
    pub struct Bar;
}

use foo::Bar;

fn my_poll() -> futures::Poll { ... }

enum SomeEnum {
    V1(usize),
    V2(String),
}

fn func() {
    let five = std::sync::Arc::new(5);
    use SomeEnum::*;
    match ... {
        V1(i) => { ... }
        V2(s) => { ... }
    }
}

그렇지만 아래 코드 역시 Rust 2018에서는 똑같은 일을 합니다. 모듈 코드를 보시죠:

// Rust 2018 (uniform paths variant)

mod submodule {
    use futures::Future;

    mod foo {
        pub struct Bar;
    }

    use foo::Bar;  <----------

    fn my_poll() -> futures::Poll { ... }

    enum SomeEnum {
        V1(usize),
        V2(String),
    }

    fn func() {
        let five = std::sync::Arc::new(5);
        use SomeEnum::*;
        match ... {
            V1(i) => { ... }
            V2(s) => { ... }
        }
    }
}

이 변경점은 프로젝트 안에서 코드를 옮기는 것을 더 쉽게 만들어주고 여러 모듈을 가지고 있는 프로젝트에서 발생할 수 있는 복잡성을 줄여주는 역할을 합니다.

만약 여러분이 외부 crate를 가지고 있고, 여러분의 프로젝트 안에 똑같은 이름을 가지고 있는 무언가가 있는 상황같이 path가 충돌하는 상황이있다면 여러분은 error를 받게 될 것입니다. 이런 상황에서 여러분은 둘 중에 하나를 하게 되는데요, 이름을 바꾸거나 path를 좀 더 명확하게 나타내는 것입니다. 후자를 선택하신 경우, ::name을 외부 crate를 위해서 사용하시거나, self::name을 내부 모듈과 그 안에 있는 것들을 위해서 사용하세요.

추가적인 visibility modifier

Minimum Rust version: 1.18

여러분은 pub키워드를 사용하여 무언가를 모듈의 public interface의 일부분으로 만들 수 있습니다. 그렇지만, 여기에 더해서, 아래와 같은 것들도 이제는 가능합니다:

pub(crate) struct Foo;

pub(in a::b::c) struct Bar;

첫번째 양식은 Foo struct를 crate전체에 대해서 public하도록 만듭니다. 그렇지만, crate밖에서는 접근할 수 없습니다. 첫번째와 비슷하게 생긴 두번째 양식은 Bar struct를 또다른 모듈 a::b::c에 대해서 public 하게 되도록 만들어줍니다.

중첩가능한 use import

Minimum Rust version: 1.25

이제 새로운 방식으로 use statement를 사용할수 있게 되었습니다: 바로 중첩입니다. 만약 여러분이 한번이라도 다음과 같은 import코드를 짜본적이 있으시다면:


# #![allow(unused_variables)]
#fn main() {
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
#}

여러분은 이제부터 이렇게 하실 수 있습니다:


# #![allow(unused_variables)]
#fn main() {
# mod foo {
// 이렇게 한줄에 적거나
use std::{fs::File, io::Read, path::{Path, PathBuf}};
# }

# mod bar {
// 여러줄에 걸쳐서
use std::{
    fs::File,
    io::Read,
    path::{
        Path,
        PathBuf
    }
};
# }
#}

우리는 이것이 쓸데없는 반복을 줄이고 코드를 약간 더 명확하게 만들 수 있을거라고 생각합니다

에러 처리와 패닉

이 챕터에서 우리는 error handling에 관한 몇가지 변화를 살펴볼 것입니다. 그 중 가장 주목할만한 변경점은 ? 연산자의 소개입니다.

더 쉬운 에러처리를 위한 ? 연산자

Minimum Rust version: 1.13 for Result<T, E>

Minimum Rust version: 1.22 for Option<T>

러스트가 쓸데 없는 코드를 줄임으로써 에러 처리를 더 쉽게 해주는 새로운 연산자, ?를 얻었습니다! 이 연산자는 이걸 간단한 하나의 문제를 해결함으로써 이 목표를 이룹니다. 예를 들어, 우리가 파일로부터 데이터를 읽어들이기 위해서 다음과 같은 코드를 짰다고 해봅시다:


# #![allow(unused_variables)]
#fn main() {
# use std::{io::{self, prelude::*}, fs::File};
fn read_username_from_file() -> Result<String, io::Error> {
    let f = File::open("username.txt");

    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();

    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}
#}

알아두세요: 이 코드는 std::fs::read_to_string을 사용하면 더 간략화될수 있습니다. 우리는 다양한 에러들이 있는 예시를 보기 위해서 위의 예시를 사용할 것입니다.

이 코드는 두 가지 경우에 대해서 에러가 발생할 수 있습니다. 파일을 열때와 파일로부터 데이터를 읽을 때죠. 어떤 에러가 발생하든지간에 우리는 에러를 read_username_from_file로부터 리턴하고 싶습니다. 이렇게 하는 것은 I/O operation의 결과에 대해서 match하는 것을 포함하죠. 그렇지만 우리가 발생한 에러를 그냥 호출한쪽에 넘기는 이렇게 간단한 경우에는 그렇게 하는 것이 코드를 너무 장황하게 보이게 만들죠.

?를 사용하면 위의 코드는 아래와 같이 간략화 될 수 있습니다:


# #![allow(unused_variables)]
#fn main() {
# use std::{io::{self, prelude::*}, fs::File};
fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = File::open("username.txt")?;
    let mut s = String::new();

    f.read_to_string(&mut s)?;

    Ok(s)
}
#}

?는 우리가 위에 썻던 매치 statement를 짧게 줄인 것입니다. 다른 말로 하자면, ?Result타입에 적용된다는거죠. 만약 그게 Ok였으면, unwrap하고 그 안의 값을 전달합니다. 만약 Err인 경우는, 속해 있는 함수가 그 에러를 리턴하도록 합니다. 시각적으로 보자면, 이게 훨씬 깔끔한 방법입니다. 이제 우리는 ?를 그냥 적는 것 만으로 우리가 에러 처리를 call stack에 넘겨준다는 것을 쉽고 간단하게 표현할 수 있게 되었습니다.

숙련된 러스트 사용자분들이라면 이게 러스트 1.0부터 사용 가능해진 try!매크로와 똑같다는 것을 눈치채셨을 겁니다. 그리고 실제로도 이 둘은 같습니다. 지금까지는 read_username_from_file은 아래와 같이 구현되어졌을 겁니다:


# #![allow(unused_variables)]
#fn main() {
# use std::{io::{self, prelude::*}, fs::File};
fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = try!(File::open("username.txt"));
    let mut s = String::new();

    try!(f.read_to_string(&mut s));

    Ok(s)
}
#}

그래서 우리가 이미 매크로가 있는데 왜 또 똑같은 일을 하는 것을 추가하냐고요? 여기엔 여러가지 이유가 있습니다. 일단 try!매크로는 무척이나 유용하고 idiomatic한 러스트 코드에 자주 등장합니다. 이 매크로가 무척 자주 쓰인다는 것에서, 우리는 프로그래밍 언어에 있어서 더 간결하고 좋은 syntax를 가지는 것이 매우 중요하다는 것을 알수 있습니다. 그리고 러스트의 강력한 매크로 시스템은 이러한 더 나은 새로운 syntax를 고안하는 것을 더 쉽게 만들어 줍니다: 예를들어서, 실험적인 언어 확장들이 고안되고 그것을 매크로로 만들면 언어설계를 바꾸지 않으면서도 그 확장들을 쓸 수 있고 만약 이 매크로들이 유용하다는 것이 입증되면 그때 그것들을 언어의 정식 일부분으로 받아들일 수 있죠. try!매크로에서 ?로의 전환이 그 좋은 예시입니다.

try!(try!(try!(foo()).bar()).baz())

as opposed to

foo()?.bar()?.baz()?

첫번째 경우에는 코드를 눈으로 가볍게 쓱 훑기가 좀 어렵습니다. 그리고 각각의 에러 레이어가 표현식 앞에 try!를 쓸때마다 붙지요. 이게 간단한 에러 처리를 아주 복잡하게 보이게 만들면서 코드 흐름을 파악하는 것을 방해합니다. 이러한 종류의 에러 처리를 포함한 method 연쇄는 builder pattern같은 경우에서 자주 나타납니다.

그렇지만 ?를 쓰면 매크로를 사용했을때와는 다르게 더 깔끔한 에러 처리를 할 수가 있죠.

You can use ? with Result<T, E>s, but also with Option<T>. In that case, ? will return a value for Some(T) and return None for None. One current restriction is that you cannot use ? for both in the same function, as the return type needs to match the type you use ? on. In the future, this restriction will be lifted. 여러분은 또한 ?Result<T, E>와 쓰는 것 뿐만이 아니라 Option<T>와도 쓰실 수 있습니다. 이 경우에는, ?Some(T)안의 값을 리턴하거나 None의 경우에는 None을 리턴합니다. 한가지 주의하셔야 할 점은 현재로써는 여러분이 ?를 하나의 함수 내부에서 ResultOption둘 다에 대해서는 쓰지 못하신다는 것인데 미래에는 이것이 가능해질 것입니다.

main과 tests 안에서 이제 ?를 쓸 수 있습니다

Minimum Rust version: 1.26

여러분은 러스트에서 에러 처리를 할 때 보통 Result<T, E>를 쓰거나 ?를 쓰실겁니다. 만약 여러분이 간단한 프로그램들을 많이 짜시거나 (많은 테스트도 같이 짜신다면), main#[test]안에서 에러 처리를 할때 짜증나셨던 경험이 있으실 겁니다.

예를 들면, 여러분은 아래와 같은 코드를 작성하신 적이 있으실 겁니다:

use std::fs::File;

fn main() {
    let f = File::open("bar.txt")?;
}

?Result를 함수를 호출한 쪽으로 넘겨주기 때문에, 위와 같은 코드는 동작하지 않습니다. 그래서 아래와 같은 컴파일 에러를 일으키지요.

error[E0277]: the `?` operator can only be used in a function that returns `Result`
              or `Option` (or another type that implements `std::ops::Try`)
 --> src/main.rs:5:13
  |
5 |     let f = File::open("bar.txt")?;
  |             ^^^^^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in a function that returns `()`
  |
  = help: the trait `std::ops::Try` is not implemented for `()`
  = note: required by `std::ops::Try::from_error`

러스트 2015에서 이 문제를 해결하기 위해서 여러분은 지금까지 아래와 같이 해오셨습니다:

// Rust 2015

# use std::process;
# use std::error::Error;

fn run() -> Result<(), Box<Error>> {
    // real logic..
    Ok(())
}

fn main() {
    if let Err(e) = run() {
        println!("Application error: {}", e);
        process::exit(1);
    }
}

그렇지만 이 경우에는, run 함수가 핵심이고 main함수는 사실상 아무것도 하지 않죠. 이 문제는 #[test]와 함께 쓰일때, 더 심각해집니다. 왜냐면 테스트들은 보통 그 수가 많으니까요.

러스트 2018에서는 이제 #[test]main안에서 Result를 리턴할 수 있습니다:

// Rust 2018

use std::fs::File;

fn main() -> Result<(), std::io::Error> {
    let f = File::open("bar.txt")?;

    Ok(())
}

이 경우에는, 예를들어 파일이 존재하지 않아서 Err(err)가 리턴되었다고 친다면, main0이 아닌 에러코드와 함께 exit 할 것이고 errDebug폼을 프린트할것입니다.

동작 원리

-> Result<...>main#[test]와 같이 쓰일 수 있도록 하기 위해서 우리가 무슨 마법을 부린 것은 아닙니다. 이러한 것들은 모두 Termination Trait 을 이용해서 작동하는데요, main의 모든 올바른 리턴 타입들과 테스트 함수들은 이 trait을 무조건 구현해야 합니다. 이 trait은 다음과 같이 정의되어 있습니다.


# #![allow(unused_variables)]
#fn main() {
pub trait Termination {
    fn report(self) -> i32;
}
#}

여러분의 프로그램의 시작 포인트를 지정할때, 컴파일러는 이 trait을 이용해서 여러분이 쓴 main함수의 Result에 대해서 .report()를 호출할겁니다.

이 trait의 Result()에 대한 두개의 간단한 구현 예시는 다음과 같습니다:


# #![allow(unused_variables)]
#fn main() {
# #![feature(process_exitcode_placeholder, termination_trait_lib)]
# use std::process::ExitCode;
# use std::fmt;
#
# pub trait Termination { fn report(self) -> i32; }

impl Termination for () {
    fn report(self) -> i32 {
        # use std::process::Termination;
        ExitCode::SUCCESS.report()
    }
}

impl<E: fmt::Debug> Termination for Result<(), E> {
    fn report(self) -> i32 {
        match self {
            Ok(()) => ().report(),
            Err(err) => {
                eprintln!("Error: {:?}", err);
                # use std::process::Termination;
                ExitCode::FAILURE.report()
            }
        }
    }
}
#}

()의 경우에는 보시다시피 success code가 그냥 리턴됩니다. Result의 경우에는 Ok인 경우엔 ()의 구현쪽으로 위임되고, Err(...)의 경우는 에러 메세지가 출력되고 failure exit code가 리턴됩니다.

더 자세한게 알고 싶으시다면 여기(the tracking issue)여기(the RFC)에 물어보세요!.

std::panic으로 panic! 컨트롤 하기

Minimum Rust version: 1.9

러스트에는 std::panic모듈이 있는데요, 이 모듈에는 panic으로부터 시작된 stack unwinding을 멈추는 method들이 들어 있습니다.


# #![allow(unused_variables)]
#fn main() {
use std::panic;

let result = panic::catch_unwind(|| {
    println!("hello!");
});
assert!(result.is_ok());

let result = panic::catch_unwind(|| {
    panic!("oh no!");
});
assert!(result.is_err());
#}

일반적으로, 러스트는 어떠한 작업이 실패하게 되는 경우를 두 가지로 분류합니다:

  • 사전에 예상된 문제로 인한 경우, 예를 들어, 파일이 발견되지 않는 경우죠.
  • 예측되지 않은 문제로 인한 경우, 예를 들면, 배열의 잘못된 인덱스를 참조하는 경우죠.

사전에 예상되었던 문제들은 주로 여러분의 프로그램이 통제할 수 있는 범위 밖에서 나타나는 문제들입니다; 잘 짜여진 프로그램은 이러한 종류의 에러에 잘 대응합니다. 러스트에서 이러한 예측가능한 에러들은 함수로 하여금 호출한 쪽에 어떠한 문제가발생했는지에 대한 정보를 넘겨줄 수 있는 Result 타입을 통해서 처리할 수 있습니다. 만약 어떠한 함수를 실행하고 오류가 리턴되면 그것에 맞는 처리를 해 주면 되지요.

사전에 예측되지 않은 에러들은 버그입니다: 이것들은 주로 assertion이나 contract가 violated되면서 나타납니다. 이러한 종류의 에러들은 사전에 예측가능하지 않기 때문에, 아까와 같은 처리 방법으로 이들을 처리할 수는 없습니다. 대신에, 러스트는 panic이라는 것을 씁니다. (panic은 기본적으로 stack unwind를 합니다, 오류가 발생한 thread의 destructor는 실행 하지만 다른 어떠한 코드도 실행하지 않습니다.) 오류가 발생하지 않은 다른 thread들은 계속 실행됩니다. 그렇지만 이러한 thread들이 channel 이나 shared memory를 통해 panic이 발생한 thread와 상호작용하는 경우 그 thread도 panic하게 됩니다. 따라서 panic은 panic이 발생한 부분과 직접적으로 연결되어 있지 않은 어떠한 지점까지 연쇄적으로 발생합니다. 그 단절되어 있는 부분은 계속 실행이 가능하고 어쩌면 이 부분이 panic으로부터 recover할 수도 있습니다. 예를 들어, 우리가 서버를 운용하고 있다고 생각해보세요. 이 경우에, 많은 thread들 중에 하나의 thread에서 panic이 발생했다고 해서 전체 서버가 동작을 멈출 필요는 없습니다.

또한 프로그램들이 unwind하는 대신에 abort할 수 있다는 것도 알아두세요, 이 경우엔 panic을 잡아내려 하는 것이 불가능할 수도 있습니다. 만약 여러분의 코드가 catch_unwind에 의존한다면, 여러분의 Cargo.toml파일에 아래의 내용을 추가하세요!

[profile.debug]
panic = "unwind"

[profile.release]
panic = "unwind"

만약 여러분의 프로그램의 사용자가 abort하는 쪽을 선택한다면, 컴파일 에러가 발생하게 될 것입니다.

catch_unwind API는 thread 안에서 위에서와 같은 panic으로부터 안전한 영역을 만들수 있게 도와주는데요, 중요한 예시들은 아래와 같습니다:

  • 다른 프로그래밍 언어에 러스트를 Embedding하는 경우
  • thread들을 다루는 abstraction
  • 테스트 프레임워크. 왜냐하면 테스트들은 panic을 할 수 있고 그게 테스트를 하는 프로그램을 죽여서는 안되기 때문이죠.

첫번째의 경우에는, 다른 언어의 영역에서 unwinding을 하는 것은 undefined behavior입니다. 그리고 이것은 주로 segmentation fault를 일으키죠. panic이 잡힐 수 있도록 한다는 것은 여러분이 안전하게 Rust코드를 C API를 통해서 노출시키거나 unwinding을 C 에서의 error로 바꿀 수 있다는 것을 의미합니다.

두번째 같은 경우에는, threadpool 라이브러리를 생각해보세요. 한 thread가 panic을 일으킨 경우, 여러분은 보통 그 thread를 죽이는 것보단 panic을 잡고 client of the pool과 communicate 하고 싶을 것입니다. catch_unwind API는 resume_unwind와 함께 쓰일수 있는데요, 이것은 패닉 과정을 그게 속하는 client of the pool에서 restart하는데 쓰일 수 있습니다.

둘 다의 경우에, 여러분은 패닉으로부터 안전한 영역을 thread안에 만드시는 겁니다. 그리고 이것이 panic을 다른 종류의 error로 바꾸는 거죠.

패닉이 발생한 경우 Abort하기

Minimum Rust version: 1.10

기본적으로(By default), 러스트 프로그램들은 panic!발생시에 stack unwind를 합니다. 만약 여러분이 이것 대신에 즉시 abort하는 것을 원하신다면 여러분은 Cargo.toml파일에 아래와 같이 적으시면 됩니다!

[profile.debug]
panic = "abort"

[profile.release]
panic = "abort"

왜 굳이 abort를 하냐고요? stasck unwinding을 하지 않음으로써, 여러분은 더 적은 사이즈의 바이너리를 얻으실 수 있습니다. 여러분은 panic을 잡아낼 수 없게 되시겠지만, 여러분이 어떤 프로젝트를 하시냐에 따라서 둘 다 올바른 선택이 될 수 있습니다.

컨트롤 플로우

이 챕터에서 우리는, 컨트롤 플로우에 대한 몇가지 개선점에 대해서 이야기해보겠습니다. 이중에서도 가장 주목해야할 변화는 아직 찾아오진 않았지만 async 그리고 await가 될 것입니다.

이제 loop가 값과 함께 break할 수 있습니다.

Minimum Rust version: 1.19

이제 loop가 값과 함께 break할 수 있습니다:


# #![allow(unused_variables)]
#fn main() {
// 이전 버전
let x;

loop {
    x = 7;
    break;
}

// 새 버전
let x = loop { break 7; };
#}

Rust는 과거부터 "expression 지향 언어"라고 말해왔는데요, 그 말은, 언어에 존재하는 대부분의 것들이 expression이며 따라서 특정한 값으로 evaluate된다는 말입니다. (statement와는 다르게 말이죠) 그렇지만 loop는 지금까지 statement였기 때문에 이러한 기조에서 벗어나 있었습니다. 이제 그것을 고치려 합니다.

지금 당장은 이것이 적용되는 것은 loop뿐입니다. whilefor에는 적용되는 사항이 아닙니다. 지금 확실히 정해진 것은 없지만, 미래에 whilefor에도 이것이 적용될지도 모릅니다.

async/await for easier concurrency

더 편리한 Concurrency를 위한 async/await

Minimum Rust version: nightly

async/await 이 두 keyword들은 Rust 2018의 처음 release에는 포함되지 않을 것입니다. 그렇지만 우리는 이 두 keyword들을 미래에 나올 release에 포함될수 있도록 미리 예약해뒀습니다. 이 두 keyword들의 등장이 가까워지면 여러분에게 공지를 드리겠습니다!

Trait 시스템

In this chapter of the guide, we discuss a few improvements to the trait system. The most notable of these is impl Trait.

impl Trait for returning complex types with ease

Minimum Rust version: 1.26

impl Trait is the new way to specify unnamed but concrete types that implement a specific trait. There are two places you can put it: argument position, and return position.

trait Trait {}

// argument position
fn foo(arg: impl Trait) {
}

// return position
fn foo() -> impl Trait {
}

Argument Position

In argument position, this feature is quite simple. These two forms are almost the same:

trait Trait {}

fn foo<T: Trait>(arg: T) {
}

fn foo(arg: impl Trait) {
}

That is, it's a slightly shorter syntax for a generic type parameter. It means, "arg is an argument that takes any type that implements the Trait trait."

However, there's also an important technical difference between T: Trait and impl Trait here. When you write the former, you can specify the type of T at the call site with turbo-fish syntax as with foo::<usize>(1). In the case of impl Trait, if it is used anywhere in the function definition, then you can't use turbo-fish at all. Therefore, you should be mindful that changing both from and to impl Trait can constitute a breaking change for the users of your code.

Return Position

In return position, this feature is more interesting. It means "I am returning some type that implements the Trait trait, but I'm not going to tell you exactly what the type is." Before impl Trait, you could do this with trait objects:


# #![allow(unused_variables)]
#fn main() {
trait Trait {}

impl Trait for i32 {}

fn returns_a_trait_object() -> Box<dyn Trait> {
    Box::new(5)
}
#}

However, this has some overhead: the Box<T> means that there's a heap allocation here, and this will use dynamic dispatch. See the dyn Trait section for an explanation of this syntax. But we only ever return one possible thing here, the Box<i32>. This means that we're paying for dynamic dispatch, even though we don't use it!

With impl Trait, the code above could be written like this:


# #![allow(unused_variables)]
#fn main() {
trait Trait {}

impl Trait for i32 {}

fn returns_a_trait_object() -> impl Trait {
    5
}
#}

Here, we have no Box<T>, no trait object, and no dynamic dispatch. But we still can obscure the i32 return type.

With i32, this isn't super useful. But there's one major place in Rust where this is much more useful: closures.

impl Trait and closures

If you need to catch up on closures, check out their chapter in the book.

In Rust, closures have a unique, un-writable type. They do implement the Fn family of traits, however. This means that previously, the only way to return a closure from a function was to use a trait object:


# #![allow(unused_variables)]
#fn main() {
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}
#}

You couldn't write the type of the closure, only use the Fn trait. That means that the trait object is necessary. However, with impl Trait:


# #![allow(unused_variables)]
#fn main() {
fn returns_closure() -> impl Fn(i32) -> i32 {
    |x| x + 1
}
#}

We can now return closures by value, just like any other type!

More details

The above is all you need to know to get going with impl Trait, but for some more nitty-gritty details: type parameters and impl Trait work slightly differently when they're in argument position versus return position. Consider this function:

fn foo<T: Trait>(x: T) {

When you call it, you set the type, T. "you" being the caller here. This signature says "I accept any type that implements Trait." ("any type" == universal in the jargon)

This version:

fn foo<T: Trait>() -> T {

is similar, but also different. You, the caller, provide the type you want, T, and then the function returns it. You can see this in Rust today with things like parse or collect:

let x: i32 = "5".parse()?;
let x: u64 = "5".parse()?;

Here, .parse has this signature:

pub fn parse<F>(&self) -> Result<F, <F as FromStr>::Err> where
    F: FromStr,

Same general idea, though with a result type and FromStr has an associated type... anyway, you can see how F is in the return position here. So you have the ability to choose.

With impl Trait, you're saying "hey, some type exists that implements this trait, but I'm not gonna tell you what it is.". So now, the caller can't choose, and the function itself gets to choose. If we tried to define parse with Result<impl F,... as the return type, it wouldn't work.

Using impl Trait in more places

As previously mentioned, as a start, you will only be able to use impl Trait as the argument or return type of a free or inherent function. However, impl Trait can't be used inside implementations of traits, nor can it be used as the type of a let binding or inside a type alias. Some of these restrictions will eventually be lifted. For more information, see the tracking issue on impl Trait.

dyn Trait for trait objects

Minimum Rust version: 1.27

The dyn Trait feature is the new syntax for using trait objects. In short:

  • Box<Trait> becomes Box<dyn Trait>
  • &Trait and &mut Trait become &dyn Trait and &mut dyn Trait

And so on. In code:


# #![allow(unused_variables)]
#fn main() {
trait Trait {}

impl Trait for i32 {}

// old
fn function1() -> Box<Trait> {
# unimplemented!()
}

// new
fn function2() -> Box<dyn Trait> {
# unimplemented!()
}
#}

That's it!

More details

Using just the trait name for trait objects turned out to be a bad decision. The current syntax is often ambiguous and confusing, even to veterans, and favors a feature that is not more frequently used than its alternatives, is sometimes slower, and often cannot be used at all when its alternatives can.

Furthermore, with impl Trait arriving, "impl Trait vs dyn Trait" is much more symmetric, and therefore a bit nicer, than "impl Trait vs Trait". impl Trait is explained here

In the new edition, you should therefore prefer dyn Trait to just Trait where you need a trait object.

More container types support trait objects

Minimum Rust version: 1.2

In Rust 1.0, only certain, special types could be used to create trait objects.

With Rust 1.2, that restriction was lifted, and more types became able to do this. For example, Rc<T>, one of Rust's reference-counted types:

use std::rc::Rc;

trait Foo {}

impl Foo for i32 {
    
}

fn main() {
    let obj: Rc<dyn Foo> = Rc::new(5);
}

This code would not work with Rust 1.0, but now works.

If you haven't seen the dyn syntax before, see the section on it. For versions that do not support it, replace Rc<dyn Foo> with Rc<Foo>.

Associated 상수

Minimum Rust version: 1.20

여러분들 모두 알다시피 여러분들은 trait, struct 그리고 enum이 "associated function"을 가지도록 할 수 있습니다:

struct Struct;

impl Struct {
    fn foo() {
        println!("foo is an associated function of Struct");
    }
}

fn main() {
    Struct::foo();
}

These are called “associated functions” because they are functions that are associated with the type, that is, they’re attached to the type itself, and not any particular instance.

Rust 1.20 adds the ability to define “associated constants” as well:

struct Struct;

impl Struct {
    const ID: u32 = 0;
}

fn main() {
    println!("the ID of Struct is: {}", Struct::ID);
}

That is, the constant ID is associated with Struct. Like functions, associated constants work with traits and enums as well.

Traits have an extra ability with associated constants that gives them some extra power. With a trait, you can use an associated constant in the same way you’d use an associated type: by declaring it, but not giving it a value. The implementor of the trait then declares its value upon implementation:

trait Trait {
    const ID: u32;
}

struct Struct;

impl Trait for Struct {
    const ID: u32 = 5;
}

fn main() {
    println!("{}", Struct::ID);
}

Before this feature, if you wanted to make a trait that represented floating point numbers, you’d have to write this:


# #![allow(unused_variables)]
#fn main() {
trait Float {
    fn nan() -> Self;
    fn infinity() -> Self;
    // ...
}
#}

This is slightly unwieldy, but more importantly, because they’re functions, they cannot be used in constant expressions, even though they only return a constant. Because of this, a design for Float would also have to include constants as well:

mod f32 {
    const NAN: f32 = 0.0f32 / 0.0f32;
    const INFINITY: f32 = 1.0f32 / 0.0f32;

    impl Float for f32 {
        fn nan() -> Self {
            f32::NAN
        }
        fn infinity() -> Self {
            f32::INFINITY
        }
    }
}

Associated constants let you do this in a much cleaner way. This trait definition:


# #![allow(unused_variables)]
#fn main() {
trait Float {
    const NAN: Self;
    const INFINITY: Self;
    // ...
}
#}

Leads to this implementation:

mod f32 {
    impl Float for f32 {
        const NAN: f32 = 0.0f32 / 0.0f32;
        const INFINITY: f32 = 1.0f32 / 0.0f32;
    }
}

much cleaner, and more versatile.

No more anonymous trait parameters

Minimum Rust version: beta

In accordance with RFC #1685, parameters in trait method declarations are no longer allowed to be anonymous.

For example, in the 2015 edition, this was allowed:


# #![allow(unused_variables)]
#fn main() {
trait Foo {
    fn foo(&self, u8);
}
#}

In the 2018 edition, all parameters must be given an argument name (even if it's just _):


# #![allow(unused_variables)]
#fn main() {
trait Foo {
    fn foo(&self, baz: u8);
}
#}

Slice patterns

Minimum Rust version: 1.26

Have you ever tried to pattern match on the contents and structure of a slice? Rust 2018 will let you do just that.

For example, say we want to accept a list of names and respond to that with a greeting. With slice patterns, we can do that easy as pie with:

fn main() {
    greet(&[]);
    // output: Bummer, there's no one here :(
    greet(&["Alan"]);
    // output: Hey, there Alan! You seem to be alone.
    greet(&["Joan", "Hugh"]);
    // output: Hello, Joan and Hugh. Nice to see you are at least 2!
    greet(&["John", "Peter", "Stewart"]);
    // output: Hey everyone, we seem to be 3 here today.
}

fn greet(people: &[&str]) {
    match people {
        [] => println!("Bummer, there's no one here :("),
        [only_one] => println!("Hey, there {}! You seem to be alone.", only_one),
        [first, second] => println!(
            "Hello, {} and {}. Nice to see you are at least 2!",
            first, second
        ),
        _ => println!("Hey everyone, we seem to be {} here today.", people.len()),
    }
}

Now, you don't have to check the length first.

We can also match on arrays like so:


# #![allow(unused_variables)]
#fn main() {
let arr = [1, 2, 3];

assert_eq!("ends with 3", match arr {
    [_, _, 3] => "ends with 3",
    [a, b, c] => "ends with something else",
});
#}

More details

Exhaustive patterns

In the first example, note in particular the _ => ... pattern. Since we are matching on a slice, it could be of any length, so we need a "catch all pattern" to handle it. If we forgot the _ => ... or identifier => ... pattern, we would instead get an error saying:

error[E0004]: non-exhaustive patterns: `&[_, _, _]` not covered

If we added a case for a slice of size 3 we would instead get:

error[E0004]: non-exhaustive patterns: `&[_, _, _, _]` not covered

and so on...

Arrays and exact lengths

In the second example above, since arrays in Rust are of known lengths, we have to match on exactly three elements. If we try to match on 2 or 4 elements,we get the errors:

error[E0527]: pattern requires 2 elements but array has 3

and

error[E0527]: pattern requires 4 elements but array has 3

In the pipeline

When it comes to slice patterns, more advanced forms are planned but have not been stabilized yet. To learn more, follow the tracking issue.

Ownership and lifetimes

In this chapter of the guide, we discuss a few improvements to ownership and lifetimes. One of the most notable of these is default match binding modes.

Non-lexical lifetimes

Minimum Rust version: beta

The borrow checker has been enhanced to accept more code, via a mechanism called "non-lexical lifetimes." Consider this example:

fn main() {
    let mut x = 5;

    let y = &x;

    let z = &mut x;
}

In older Rust, this is a compile-time error:

error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
 --> src/main.rs:5:18
  |
4 |     let y = &x;
  |              - immutable borrow occurs here
5 |     let z = &mut x;
  |                  ^ mutable borrow occurs here
6 | }
  | - immutable borrow ends here

This is because lifetimes follow "lexical scope"; that is, the borrow from y is considered to be held until y goes out of scope at the end of main, even though we never use y again. This code is fine, but the borrow checker could not handle it.

Today, this code will compile just fine.

Better errors

What if we did use y, like this?

fn main() {
    let mut x = 5;
    let y = &x;
    let z = &mut x;
    
    println!("y: {}", y);
}

Here's the error:

error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
 --> src/main.rs:5:18
  |
4 |     let y = &x;
  |              - immutable borrow occurs here
5 |     let z = &mut x;
  |                  ^ mutable borrow occurs here
...
8 | }
  | - immutable borrow ends here

With non-lexical lifetimes, the error changes slightly:

error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
 --> src/main.rs:5:13
  |
4 |     let y = &x;
  |             -- immutable borrow occurs here
5 |     let z = &mut x;
  |             ^^^^^^ mutable borrow occurs here
6 |     
7 |     println!("y: {}", y);
  |                       - borrow later used here

Instead of pointing to where y goes out of scope, it shows you where the conflicting borrow occurs. This makes these sorts of errors far easier to debug.

Default match bindings

Minimum Rust version: 1.26

Have you ever had a borrowed Option<T> and tried to match on it? You probably wrote this:

let s: &Option<String> = &Some("hello".to_string());

match s {
    Some(s) => println!("s is: {}", s),
    _ => (),
};

In Rust 2015, this would fail to compile, and you would have to write the following instead:

// Rust 2015

let s: &Option<String> = &Some("hello".to_string());

match s {
    &Some(ref s) => println!("s is: {}", s),
    _ => (),
};

Rust 2018, by contrast, will infer the &s and refs, and your original code will Just Work.

This affects not just match, but patterns everywhere, such as in let statements, closure arguments, and for loops.

More details

The mental model of patterns has shifted a bit with this change, to bring it into line with other aspects of the language. For example, when writing a for loop, you can iterate over borrowed contents of a collection by borrowing the collection itself:

let my_vec: Vec<i32> = vec![0, 1, 2];

for x in &my_vec { ... }

The idea is that an &T can be understood as a borrowed view of T, and so when you iterate, match, or otherwise destructure a &T you get a borrowed view of its internals as well.

More formally, patterns have a "binding mode," which is either by value (x), by reference (ref x), or by mutable reference (ref mut x). In Rust 2015, match always started in by-value mode, and required you to explicitly write ref or ref mut in patterns to switch to a borrowing mode. In Rust 2018, the type of the value being matched informs the binding mode, so that if you match against an &Option<String> with a Some variant, you are put into ref mode automatically, giving you a borrowed view of the internal data. Similarly, &mut Option<String> would give you a ref mut view.

'_, the anonymous lifetime

Minimum Rust version: nightly

Rust 2018 allows you to explicitly mark where a lifetime is elided, for types where this elision might otherwise be unclear. To do this, you can use the special lifetime '_ much like you can explicitly mark that a type is inferred with the syntax let x: _ = ..;.

Let's say, for whatever reason, that we have a simple wrapper around &'a str:


# #![allow(unused_variables)]
#fn main() {
struct StrWrap<'a>(&'a str);
#}

In Rust 2015, you might have written:


# #![allow(unused_variables)]
#fn main() {
// Rust 2015

use std::fmt;

# struct StrWrap<'a>(&'a str);

fn make_wrapper(string: &str) -> StrWrap {
    StrWrap(string)
}

impl<'a> fmt::Debug for StrWrap<'a> {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.write_str(self.0)
    }
}
#}

In Rust 2018, you can instead write:


# #![allow(unused_variables)]
#![feature(rust_2018_preview)]

#fn main() {
# use std::fmt;
# struct StrWrap<'a>(&'a str);

// Rust 2018

fn make_wrapper(string: &str) -> StrWrap<'_> {
    StrWrap(string)
}

impl fmt::Debug for StrWrap<'_> {
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt.write_str(self.0)
    }
}
#}

More details

In the Rust 2015 snippet above, we've used -> StrWrap. However, unless you take a look at the definition of StrWrap, it is not clear that the returned value is actually borrowing something. Therefore, starting with Rust 2018, it is deprecated to leave off the lifetime parameters for non-reference-types (types other than & and &mut). Instead, where you previously wrote -> StrWrap, you should now write -> StrWrap<'_>, making clear that borrowing is occurring.

What exactly does '_ mean? It depends on the context! In output contexts, as in the return type of make_wrapper, it refers to a single lifetime for all "output" locations. In input contexts, a fresh lifetime is generated for each "input location". More concretely, to understand input contexts, consider the following example:


# #![allow(unused_variables)]
#fn main() {
// Rust 2015

struct Foo<'a, 'b: 'a> {
    field: &'a &'b str,
}

impl<'a, 'b: 'a> Foo<'a, 'b> {
    // some methods...
}
#}

We can rewrite this as:


# #![allow(unused_variables)]
#![feature(rust_2018_preview)]

#fn main() {
# struct Foo<'a, 'b: 'a> {
#     field: &'a &'b str,
# }

// Rust 2018

impl Foo<'_, '_> {
    // some methods...
}
#}

This is the same, because for each '_, a fresh lifetime is generated. Finally, the relationship 'a: 'b which the struct requires must be upheld.

For more details, see the tracking issue on In-band lifetime bindings.

Lifetime elision in impl

Minimum Rust version: 1.31

When writing impl blocks, you can now elide lifetime annotations in some situations.

Consider a trait like MyIterator:

trait MyIterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

In Rust 2015, if we wanted to implement this iterator for mutable references to Iterators, we'd need to write this:

impl<'a, I: MyIterator> MyIterator for &'a mut I {
    type Item = I::Item;
    fn next(&mut self) -> Option<Self::Item> {
        (*self).next()
    }
}

Note all of the 'a annotations. In Rust 2018, we can write this:

impl<I: MyIterator> MyIterator for &mut I {
    type Item = I::Item;
    fn next(&mut self) -> Option<Self::Item> {
        (*self).next()
    }
}

Similarly, lifetime annotations can appear due to a struct that contains references:

struct SetOnDrop<'a, T> {
    borrow: &'a mut T,
    value: Option<T>,
}

In Rust 2015, to implement Drop on this struct, we'd write:

impl<'a, T> Drop for SetOnDrop<'a, T> {
    fn drop(&mut self) {
        if let Some(x) = self.value.take() {
            *self.borrow = x;
        }
    }
}

But in Rust 2018, we can combine elision with the anonymous lifetime and write this instead.

impl<T> Drop for SetOnDrop<'_, T> {
    fn drop(&mut self) {
        if let Some(x) = self.value.take() {
            *self.borrow = x;
        }
    }
}

T: 'a inference in structs

Minimum Rust version: beta

An annotation in the form of T: 'a, where T is either a type or another lifetime, is called an "outlives" requirement. Note that "outlives" also implies 'a: 'a.

One way in which edition 2018 helps you out in maintaining flow when writing programs is by removing the need to explicitly annotate these T: 'a outlives requirements in struct definitions. Instead, the requirements will be inferred from the fields present in the definitions.

Consider the following struct definitions in Rust 2015:


# #![allow(unused_variables)]
#fn main() {
// Rust 2015

struct Ref<'a, T: 'a> {
    field: &'a T
}

// or written with a `where` clause:

struct WhereRef<'a, T> where T: 'a {
    data: &'a T
}

// with nested references:

struct RefRef<'a, 'b: 'a, T: 'b> {
    field: &'a &'b T,
}

// using an associated type:

struct ItemRef<'a, T: Iterator>
where
    T::Item: 'a
{
    field: &'a T::Item
}
#}

In Rust 2018, since the requirements are inferred, you can instead write:

// Rust 2018

struct Ref<'a, T> {
    field: &'a T
}

struct WhereRef<'a, T> {
    data: &'a T
}

struct RefRef<'a, 'b, T> {
    field: &'a &'b T,
}

struct ItemRef<'a, T: Iterator> {
    field: &'a T::Item
}

If you prefer to be more explicit in some cases, that is still possible.

More details

For more details, see the tracking issue and the RFC.

Simpler lifetimes in static and const

Minimum Rust version: 1.17

In older Rust, you had to explicitly write the 'static lifetime in any static or const that needed a lifetime:


# #![allow(unused_variables)]
#fn main() {
# mod foo {
const NAME: &'static str = "Ferris";
# }
# mod bar {
static NAME: &'static str = "Ferris";
# }
#}

But 'static is the only possible lifetime there. So Rust now assumes the 'static lifetime, and you don't have to write it out:


# #![allow(unused_variables)]
#fn main() {
# mod foo {
const NAME: &str = "Ferris";
# }
# mod bar {
static NAME: &str = "Ferris";
# }
#}

In some situations, this can remove a lot of boilerplate:


# #![allow(unused_variables)]
#fn main() {
# mod foo {
// old
const NAMES: &'static [&'static str; 2] = &["Ferris", "Bors"];
# }
# mod bar {

// new
const NAMES: &[&str; 2] = &["Ferris", "Bors"];
# }
#}

데이터 타입

이 챕터에서는요, 우리는 데이터 타입들에 관한 몇가지 작은 개선점들에 대해서 살펴볼것입니다. 그 중의 하나는 field-init-shorthand입니다.

Struct의 field초기화가 쉬워집니다

Minimum Rust version: 1.17

기존의 Rust에서는, struct를 초기화 할때는, 여러분은 무조건 key: value 쌍을 입력해야만 했습니다:


# #![allow(unused_variables)]
#fn main() {
struct Point {
    x: i32,
    y: i32,다
}

let a = 5;
let b = 6;

let p = Point {
    x: a,
    y: b,
};
#}

그렇지만, 때때로 이러한 변수들이 field들과 같은 이름을 가지고 있을 때가 있죠. 그래서 여러분은 결과적으로 다음과 같은 코드를 짠 경험이 분명 있을겁니다.

let p = Point {
    x: x,
    y: y,
};

지금부터는, 만약 변수들과 field들이 똑같은 이름을 가지고 있다면, 여러분은 둘 다 써야할 필요가 없습니다. 아래와 같이 하나만 적으면 됩니다:


# #![allow(unused_variables)]
#fn main() {
struct Point {
    x: i32,
    y: i32,
}

let x = 5;
let y = 6;

// new
let p = Point {
    x,
    y,
};
#}

..= for inclusive ranges

Minimum Rust version: 1.26

예전부터 여러분은 exclusive range를 ".."를 이용해서 다음과 같이 만들어오셨을 겁니다:

for i in 1..3 {
    println!("i: {}", i);
}

이 코드는 i: 1 그리고 i: 2를 출력합니다. 지금부터는 여러분은 inclusive range를 다음과 같이 사용하실 수 있습니다:


# #![allow(unused_variables)]
#fn main() {
for i in 1..=3 {
    println!("i: {}", i);
}
#}

이 코드는 이전의 코드와 같이 i: 1 그리고 i: 2를 출력하고 i: 3까지 출력할것입니다. 이러한 inclusive range는 여러분이 range범위 안의 모든 가능한 경우에 대해서 모두 iterate하고 싶을때 특히 유용할 수 있습니다. 예를 들면, 아래와 같은 놀라운 Rust 프로그램을 보시죠:

fn takes_u8(x: u8) {
    // ...
}

fn main() {
    for i in 0..256 {
        println!("i: {}", i);
        takes_u8(i);
    }
}

이 프르그램이 무엇을 하는지 아시겠나요? 정답은요... 아무것도 안합니다! 하하 우리는 이 프로그램을 컴파일 하려할때 이 warning을 받게 되는 데요, 거기에 힌트가 쓰여 있습니다:

warning: literal out of range for u8
 --> src/main.rs:6:17
  |
6 |     for i in 0..256 {
  |                 ^^^
  |
  = note: #[warn(overflowing_literals)] on by default

아아, iu8이기 때문에 오버플로우가 발생하는군요. 따라서 이는 for i in 0..0라고 적는 것과 마찬가지입니다. 그래서 아무 일도 일어나지 않는 것이죠.

그런데 만약 우리가 여기에 inclusive range를 적용하면 어떻게 될까요?:

fn takes_u8(x: u8) {
    // ...
}

fn main() {
    for i in 0..=255 {
        println!("i: {}", i);
        takes_u8(i);
    }
}

이 코드는 여러분이 처음에 예상하셨을 256개의 line들을 정상적으로 출력할 것입니다.

128 비트 정수

Minimum Rust version: 1.26

굉장히 간단합니다: 이제 Rust에 128 비트 정수가 있습니다!


# #![allow(unused_variables)]
#fn main() {
let x: i128 = 0;
let y: u128 = 0;
#}

이들은 u64의 두배의 크기를 가지기 때문에 저장할 수 있는 값의 범위가 더 큽니다. 정확히 말하자면 아래와 같죠.

  • u128: 0 - 340,282,366,920,938,463,463,374,607,431,768,211,455
  • i128: −170,141,183,460,469,231,731,687,303,715,884,105,728 - 170,141,183,460,469,231,731,687,303,715,884,105,727

엄청나네요!!

이제 복합 연산자들이 implementable 합니다.

Minimum Rust version: 1.8

The various “operator equals” operators, such as += and -=, are implementable via various traits. For example, to implement += on a type of your own: +=-=같은 다양한 복합 연산자들이 이제 trait를 통해서 implementable합니다. 예를 들면, +=를 여러분의 custom type에 대해서 implement하는 코드를 보세요:

use std::ops::AddAssign;

#[derive(Debug)]
struct Count { 
    value: i32,
}

impl AddAssign for Count {
    fn add_assign(&mut self, other: Count) {
        self.value += other.value;
    }
}

fn main() {
    let mut c1 = Count { value: 1 };
    let c2 = Count { value: 5 };

    c1 += c2;

    println!("{:?}", c1);
}

위의 코드는 Count { value: 6 }를 출력할 것입니다.

enum의 Unsafe 버전인 union!

Minimum Rust version: 1.19

이제 Rust가 union을 지원합니다!:


# #![allow(unused_variables)]
#fn main() {
union MyUnion {
    f1: u32,
    f2: f32,
}
#}

union들은 enum과 비슷하지만 "untagged"되어있다는 점에서 다릅니다. Enum들은 Runtime에 어떤 Variant가 맞는 것인지 저장하는 "tag"를 가지고 있는 반면에 union들은 이러한 "tag"가 없습니다.

우리가 union안에 있는 데이터를 잘못된 variant로 해석할 수 있고 Rust가 우리를 대신해서 이것을 확인해줄 수 없기 때문에 union의 field를 읽는 것은 unsafe입니다:


# #![allow(unused_variables)]
#fn main() {
# union MyUnion {
#     f1: u32,
#     f2: f32,
# }
let mut u = MyUnion { f1: 1 };

u.f1 = 5;

let value = unsafe { u.f1 };
#}

패턴 매칭도 잘 동작합니다!:


# #![allow(unused_variables)]
#fn main() {
# union MyUnion {
#     f1: u32,
#     f2: f32,
# }
fn f(u: MyUnion) {
    unsafe {
        match u {
            MyUnion { f1: 10 } => { println!("ten"); }
            MyUnion { f2 } => { println!("{}", f2); }
        }
    }
}
#}

When are unions useful? One major use-case is interoperability with C. C APIs can (and depending on the area, often do) expose unions, and so this makes writing API wrappers for those libraries significantly easier. Additionally, unions also simplify Rust implementations of space-efficient or cache-efficient structures relying on value representation, such as machine-word-sized unions using the least-significant bits of aligned pointers to distinguish cases.

미래에 union에 더 많은 개선점들이 추가될것입니다. 아직까지는 union들은 Copy타입만 포함할 수 있고, 또한 Drop을 implement하지 못합니다. 우리는 이러한 제약점들을 미래에는 걷어낼수 있을거라고 기대하고 있습니다.

repr attribute로 alignment 설정하기

Minimum Rust version: 1.25

From Wikipedia:

현대 컴퓨터 하드웨어안의 CPU는 메모리 안의 데이터가 적절하게 aligned되어 있을 때 메모리 읽기연산과 쓰기연산을 가장 효과적으로 수행할 수 있습니다. 일반적으로 말하자면 이것은 메모리상의 주소가 데이터의 크기의 배수로 되어있는 경우를 말합니다. Data alignment란 데이터들을 그들의 natural alignment에 맞춰서 align하는 것을 말하는데요, 이 natural alignment를 보장하기 위해서 우리는 때때로 데이터 사이, 또는 데이터의 가장 마지막 요소 뒤에 빈 공간(padding)을 넣습니다.

#[repr] attribute는 새로운 parameter를 갖게 되었는데요, struct의 alignment를 설정하는 align입니다:


# #![allow(unused_variables)]
#fn main() {
struct Number(i32);

assert_eq!(std::mem::align_of::<Number>(), 4);
assert_eq!(std::mem::size_of::<Number>(), 4);

#[repr(align(16))]
struct Align16(i32);

assert_eq!(std::mem::align_of::<Align16>(), 16);
assert_eq!(std::mem::size_of::<Align16>(), 16);
#}

만약 여러분이 저수준작업을 하고 있다면, 이러한 것이 여러분에게 굉장히 중요할수도 있습니다.

이러한 alignment는 일반적으로 걱정할 거리가 아닌데요, 컴파일러가 알아서 일반적인 경우에 그에 맞는 alignment 를 설정하기 때문이죠. 그렇지만, 때때로 우리는 foreign system과 함께 동작해야하는 경우같이 일반적인 alignment가 아닌 alignment가 필요한 경우들과 맞닥뜨립니다. 예를 들면, 아래와 같은 상황들이 custom alignment가 필요하거나 있으면 더 편해지는 경우들이죠.

  • Hardware can often have obscure requirements such as "this structure is aligned to 32 bytes" when it in fact is only composed of 4-byte values. While this can typically be manually calculated and managed, it's often also useful to express this as a property of a type to get the compiler to do a little extra work instead.
  • gccclang 같은 C 컴파일러들은 structure들의 alignment를 사용자 마음대로 바꿀 수 있도록 허용합니다. Rust도 이러한 custom alignment를 허용한다면 C 코드와 상호작용하는 것이 더 쉬워지겠죠? (예를 들면 정확하게 C로 structure를 건네주는 것이 더 쉬워집니다)
  • Custom alignment can often be used for various tricks here and there and is often convenient as "let's play around with an implementation" tool. For example this can be used to statically allocate page tables in a kernel or create an at-least cache-line-sized structure easily for concurrent programming.

이 기능의 목적은 위와 같은 상황들을 더 쉽게 다루기 위해 컴파일러가 기본적으로 선택하는 alignment를 바꿀 수 있는 가볍고 단순한 방법을 제공하는 것입니다.

더 빠른 연산을 위한 SIMD

Minimum Rust version: 1.27

기본적인 SIMD가 이제 가능합니다! SIMD란 "하나의 CPU명령어로 여러개의 데이터를 한꺼번에 처리하는 것"을 말하는 데요, 예를 들어서 다음과 같은 함수가 있다고 해 봅시다:


# #![allow(unused_variables)]
#fn main() {
pub fn foo(a: &[u8], b: &[u8], c: &mut [u8]) {
    for ((a, b), c) in a.iter().zip(b).zip(c) {
        *c = *a + *b;
    }
}
#}

Here, we’re taking two slices, and adding the numbers together, placing the result in a third slice. The simplest possible way to do this would be to do exactly what the code does, and loop through each set of elements, add them together, and store it in the result. However, compilers can often do better. LLVM will usually “autovectorize” code like this, which is a fancy term for “use SIMD.” Imagine that a and b were both 16 elements long. Each element is a u8, and so that means that each slice would be 128 bits of data. Using SIMD, we could put both a and b into 128 bit registers, add them together in a single instruction, and then copy the resulting 128 bits into c. That’d be much faster!

While stable Rust has always been able to take advantage of autovectorization, sometimes, the compiler just isn’t smart enough to realize that we can do something like this. Additionally, not every CPU has these features, and so LLVM may not use them so your program can be used on a wide variety of hardware. The std::arch module allows us to use these kinds of instructions directly, which means we don’t need to rely on a smart compiler. Additionally, it includes some features that allow us to choose a particular implementation based on various criteria. For example:

#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"),
      target_feature = "avx2"))]
fn foo() {
    #[cfg(target_arch = "x86")]
    use std::arch::x86::_mm256_add_epi64;
    #[cfg(target_arch = "x86_64")]
    use std::arch::x86_64::_mm256_add_epi64;

    unsafe {
        _mm256_add_epi64(...);
    }
}

Here, we use cfg flags to choose the correct version based on the machine we’re targeting; on x86 we use that version, and on x86_64 we use its version. We can also choose at runtime:

fn foo() {
    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
    {
        if is_x86_feature_detected!("avx2") {
            return unsafe { foo_avx2() };
        }
    }

    foo_fallback();
}

Here, we have two versions of the function: one which uses AVX2, a specific kind of SIMD feature that lets you do 256-bit operations. The is_x86_feature_detected! macro will generate code that detects if your CPU supports AVX2, and if so, calls the foo_avx2 function. If not, then we fall back to a non-AVX implementation, foo_fallback. This means that our code will run super fast on CPUs that support AVX2, but still work on ones that don’t, albeit slower.

If all of this seems a bit low-level and fiddly, well, it is! std::arch is specifically primitives for building these kinds of things. We hope to eventually stabilize a std::simd module with higher-level stuff in the future. But landing the basics now lets the ecosystem experiment with higher level libraries starting today. For example, check out the faster crate. Here’s a code snippet with no SIMD:

let lots_of_3s = (&[-123.456f32; 128][..]).iter()
    .map(|v| {
        9.0 * v.abs().sqrt().sqrt().recip().ceil().sqrt() - 4.0 - 2.0
    })
    .collect::<Vec<f32>>();

To use SIMD with this code via faster, you’d change it to this:

let lots_of_3s = (&[-123.456f32; 128][..]).simd_iter()
    .simd_map(f32s(0.0), |v| {
        f32s(9.0) * v.abs().sqrt().rsqrt().ceil().sqrt() - f32s(4.0) - f32s(2.0)
    })
    .scalar_collect();

It looks almost the same: simd_iter instead of iter, simd_map instead of map, f32s(2.0) instead of 2.0. But you get a SIMD-ified version generated for you.

Beyond that, you may never write any of this yourself, but as always, the libraries you depend on may. For example, the regex crate contains these SIMD speedups without you needing to do anything at all!

매크로

이 챕터에서 우리는 매크로 시스템에 찾아온 몇가지 개선점들에 대해서 이야기 해볼 것입니다. 주목할 만한 추가사항은 custom derive macros입니다.

Custom Derive

Minimum Rust version: 1.15

Rust에서 여러분은 아래와 같은 코드를 써보신적이 많으실 겁니다. (특정한 trait들을 자동적으로 implement하게 해주는 기능이지요):


# #![allow(unused_variables)]
#fn main() {
#[derive(Debug)]
struct Pet {
    name: String,
}
#}

위와 같이 하면 Debug trait가 Pet에 대해서 아주 손쉽게 implement됩니다. 만약, derive가 없었다면, 여러분은 아래와 같이 썼어야만 했을 겁니다:


# #![allow(unused_variables)]
#fn main() {
use std::fmt;

struct Pet {
    name: String,
}

impl fmt::Debug for Pet {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Pet { name } => {
                let mut debug_trait_builder = f.debug_struct("Pet");

                let _ = debug_trait_builder.field("name", name);

                debug_trait_builder.finish()
            }
        }
    }
}
#}

숨막히네요!

매우 편리한 기능이지만, 여기에는 문제가 하나 있습니다. 바로 standard library에 있는 것들만이 자동적으로 implement될 수 있다는 것입니다. 여러분이 만든 custom 타입에는 쓰일 수 없다는 말이지요. 그렇지만 이제부터는, 여러분은 다른 누군가가 여러분의 타입을 derive하고 싶을때 어떻게 하고 싶은지 Rust에게 말해 주실 수 있습니다. 이러한 기능은 유명한 crate들인 serdeDiesel에서 정말 많이 쓰이지요.

더 자세한 것이 알고 싶으시다면, 다음 링크를 참고하세요. Rust 언어.

매크로의 달라진 점

Minimum Rust version: beta

macro_rules! style macros

In Rust 2018, you can import specific macros from external crates via use statements, rather than the old #[macro_use] attribute.

For example, consider a bar crate that implements a baz! macro. In src/lib.rs:


# #![allow(unused_variables)]
#fn main() {
#[macro_export]
macro_rules! baz {
    () => ()
}
#}

In your crate, you would have written

// Rust 2015

#[macro_use]
extern crate bar;

fn main() {
    baz!();
}

Now, you write:

// Rust 2018

use bar::baz;

fn main() {
    baz!();
}

This moves macro_rules macros to be a bit closer to other kinds of items.

Note that you'll still need #[macro_use] to use macros you've defined in your own crate; this feature only works for importing macros from external crates.

Procedural macros

When using procedural macros to derive traits, you will have to name the macro that provides the custom derive. This generally matches the name of the trait, but check with the documentation of the crate providing the derives to be sure.

For example, with Serde you would have written

// Rust 2015
extern crate serde;
#[macro_use] extern crate serde_derive;

#[derive(Serialize, Deserialize)]
struct Bar;

Now, you write instead:

// Rust 2018
use serde_derive::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct Bar;

More details

This only works for macros defined in external crates. For macros defined locally, #[macro_use] mod foo; is still required, as it was in Rust 2015.

Local helper macros

Sometimes it is helpful or necessary to have helper macros inside your module. This can make supporting both versions of rust more complicated.

For example, let's make a simplified (and slightly contrived) version of the log crate in 2015 edition style:


# #![allow(unused_variables)]
#fn main() {
use std::fmt;

/// How important/severe the log message is.
#[derive(Copy, Clone)]
pub enum LogLevel {
    Warn,
    Error
}

impl fmt::Display for LogLevel {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            LogLevel::Warn => write!(f, "warning"),
            LogLevel::Error => write!(f, "error"),
        }
    }
}

// A helper macro to log the message.
#[doc(hidden)]
#[macro_export]
macro_rules! __impl_log {
    ($level:expr, $msg:expr) => {{
        println!("{}: {}", $level, $msg)
    }}
}

/// Warn level log message
#[macro_export]
macro_rules! warn {
    ($($args:tt)*) => {
        __impl_log!($crate::LogLevel::Warn, format_args!($($args)*))
    }
}

/// Error level log message
#[macro_export]
macro_rules! error {
    ($($args:tt)*) => {
        __impl_log!($crate::LogLevel::Error, format_args!($($args)*))
    }
}
#}

Our __impl_log! macro is private to our module, but needs to be exported as it is called by other macros, and in 2015 edition all used macros must be exported.

Now, in 2018 this example will not compile:

use log::error;

fn main() {
    error!("error message");
}

will give an error message about not finding the __impl_log! macro. This is because unlike in the 2015 edition, macros are namespaced and we must import them. We could do

use log::{__impl_log, error};

which would make our code compile, but __impl_log is meant to be an implementation detail!

Macros with $crate:: prefix.

The cleanest way to handle this situation is to use the $crate:: prefix for macros, the same as you would for any other path. Versions of the compiler >= 1.30 will handle this in both editions:


# #![allow(unused_variables)]
#fn main() {
macro_rules! warn {
    ($($args:tt)*) => {
        $crate::__impl_log!($crate::LogLevel::Warn, format_args!($($args)*))
    }
}

// ...
#}

However, this will not work for older versions of the compiler that don't understand the $crate:: prefix for macros.

Macros using local_inner_macros

We also have the local_inner_macros modifier that we can add to our #[macro_export] attribute. This has the advantage of working with older rustc versions (older versions just ignore the extra modifier). The downside is that it's a bit messier:

#[macro_export(local_inner_macros)]
macro_rules! warn {
    ($($args:tt)*) => {
        __impl_log!($crate::LogLevel::Warn, format_args!($($args)*))
    }
}

So the code knows to look for any macros used locally. But wait - this won't compile, because we use the format_args! macro that isn't in our local crate (hence the convoluted example). The solution is to add a level of indirection: we create a macro that wraps format_args, but is local to our crate. That way everything works in both editions (sadly we have to pollute the global namespace a bit, but that's ok).


# #![allow(unused_variables)]
#fn main() {
// I've used the pattern `_<my crate  name>__<macro name>` to name this macro, hopefully avoiding
// name clashes.
#[doc(hidden)]
#[macro_export]
macro_rules! _log__format_args {
    ($($inner:tt)*) => {
        format_args! { $($inner)* }
    }
}
#}

Here we're using the most general macro pattern possible, a list of token trees. We just pass whatever tokens we get to the inner macro, and rely on it to report errors.

So the full 2015/2018 working example would be:


# #![allow(unused_variables)]
#fn main() {
use std::fmt;

/// How important/severe the log message is.
#[derive(Debug, Copy, Clone)]
pub enum LogLevel {
    Warn,
    Error
}

impl fmt::Display for LogLevel {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            LogLevel::Warn => write!(f, "warning"),
            LogLevel::Error => write!(f, "error"),
        }
    }
}

// A helper macro to log the message.
#[doc(hidden)]
#[macro_export]
macro_rules! __impl_log {
    ($level:expr, $msg:expr) => {{
        println!("{}: {}", $level, $msg)
    }}
}

/// Warn level log message
#[macro_export(local_inner_macros)]
macro_rules! warn {
    ($($args:tt)*) => {
        __impl_log!($crate::LogLevel::Warn, _log__format_args!($($args)*))
    }
}

/// Error level log message
#[macro_export(local_inner_macros)]
macro_rules! error {
    ($($args:tt)*) => {
        __impl_log!($crate::LogLevel::Error, _log__format_args!($($args)*))
    }
}

#[doc(hidden)]
#[macro_export]
macro_rules! _log__format_args {
    ($($inner:tt)*) => {
        format_args! { $($inner)* }
    }
}
#}

Once everyone is using a rustc version >= 1.30, we can all just use the $crate:: method (2015 crates are guaranteed to carry on compiling fine with later versions of the compiler). We need to wait for package managers and larger organisations to update their compilers before this happens, so in the mean time we can use the local_inner_macros method to support everybody. :)

The compiler

In this chapter of the guide, we discuss a few improvements to the compiler. A notable addition here is our new and improved error messages.

Improved error messages

Minimum Rust version: 1.12

We're always working on error improvements, and there are little improvements in almost every Rust version, but in Rust 1.12, a significant overhaul of the error message system was created.

For example, here's some code that produces an error:

fn main() {
    let mut x = 5;

    let y = &x;

    x += 1;
}

Here's the error in Rust 1.11:

foo.rs:6:5: 6:11 error: cannot assign to `x` because it is borrowed [E0506]
foo.rs:6     x += 1;
             ^~~~~~
foo.rs:4:14: 4:15 note: borrow of `x` occurs here
foo.rs:4     let y = &x;
                      ^
foo.rs:6:5: 6:11 help: run `rustc --explain E0506` to see a detailed explanation

Here's the error in Rust 1.28:

error[E0506]: cannot assign to `x` because it is borrowed
 --> foo.rs:6:5
  |
4 |     let y = &x;
  |              - borrow of `x` occurs here
5 |
6 |     x += 1;
  |     ^^^^^^ assignment to borrowed `x` occurs here

error: aborting due to previous error

This error isn't terribly different, but shows off how the format has changed. It shows off your code in context, rather than just showing the text of the lines themselves.

Incremental Compilation

Minimum Rust version: 1.24

Back in September of 2016, we blogged about Incremental Compilation. While that post goes into the details, the idea is basically this: when you’re working on a project, you often compile it, then change something small, then compile again. Historically, the compiler has compiled your entire project, no matter how little you’ve changed the code. The idea with incremental compilation is that you only need to compile the code you’ve actually changed, which means that that second build is faster.

This is now turned on by default. This means that your builds should be faster! Don’t forget about cargo check when trying to get the lowest possible build times.

This is still not the end story for compiler performance generally, nor incremental compilation specifically. We have a lot more work planned in the future.

One small note about this change: it makes builds faster, but makes the final binary a bit slower. For this reason, it's not turned on in release builds.

An attribute for deprecation

Minimum Rust version: 1.9

If you're writing a library, and you'd like to deprecate something, you can use the deprecated attribute:


# #![allow(unused_variables)]
#fn main() {
#[deprecated(
    since = "0.2.1",
    note = "Please use the bar function instead"
)]
pub fn foo() {
    // ...
}
#}

This will give your users a warning if they use the deprecated functionality:

   Compiling playground v0.0.1 (file:///playground)
warning: use of deprecated item 'foo': Please use the bar function instead
  --> src/main.rs:10:5
   |
10 |     foo();
   |     ^^^
   |
   = note: #[warn(deprecated)] on by default

Both since and note are optional.

since can be in the future; you can put whatever you'd like, and what's put in there isn't checked.

Rustup for managing Rust versions

Minimum Rust version: various (this tool has its own versioning scheme and works with all Rust versions)

The Rustup tool has become the recommended way to install Rust, and is advertised on our website. Its powers go further than that though, allowing you to manage various versions, components, and platforms.

For installing Rust

To install Rust through Rustup, you can go to https://www.rust-lang.org/install.html, which will let you know how to do so on your platform. This will install both rustup itself and the stable version of rustc and cargo.

To install a specific Rust version, you can use rustup install:

$ rustup install 1.30.0

This works for a specific nightly, as well:

$ rustup install nightly-2018-08-01

As well as any of our release channels:

$ rustup install stable
$ rustup install beta
$ rustup install nightly

For updating your installation

To update all of the various channels you may have installed:

$ rustup update

This will look at everything you've installed, and if there are new releases, will update anything that has one.

Managing versions

To set the default toolchain to something other than stable:

$ rustup toolchain default nightly

To use a toolchain other than the default, use rustup run:

$ rustup run nightly cargo build

There's also an alias for this that's a little shorter:

$ cargo +nightly build

If you'd like to have a different default per-directory, that's easy too! If you run this inside of a project:

$ rustup override set nightly

Then when you're in that directory, any invocations of rustc or cargo will use that toolchain. To share this with others, you can create a rust-toolchain file with the contents of a toolchain, and check it into source control. Now, when someone clones your project, they'll get the right version without needing to override set themselves.

Installing other targets

Rust supports cross-compiling to other targets, and Rustup can help you manage them. For example, to use MUSL:

$ rustup target add x86_64-unknown-linux-musl

And then you can

$ cargo build --target=x86_64-unknown-linux-musl

To see the full list of targets you can install:

$ rustup target list

Installing components

Components are used to install certain kinds of tools. While cargo-install has you covered for most tools, some tools need deep integration into the compiler. Rustup knows exactly what version of the compiler you're using, and so it's got just the information that these tools need.

Components are per-toolchain, so if you want them to be available to more than one toolchain, you'll need to install them multiple times. In the following examples, add a --toolchain flag, set to the toolchain you want to install for, nightly for example. Without this flag, it will install the component for the default toolchain.

To see the full list of components you can install:

$ rustup component list

Next, let's talk about some popular components and when you might want to install them.

rust-docs, for local documentation

This first component is installed by default when you install a toolchain. It contains a copy of Rust's documentation, so that you can read it offline.

This component cannot be removed for now; if that's of interest, please comment on this issue.

rust-src for a copy of Rust's source code

The rust-src component can give you a local copy of Rust's source code. Why might you need this? Well, autocompletion tools like Racer use this information to know more about the functions you're trying to call.

$ rustup component add rust-src

The "preview" components

There are several components in a "preview" stage. These components currently have -preview in their name, and this indicates that they're not quite 100% ready for general consumption yet. Please try them out and give us feedback, but know that they do not follow Rust's stability guarantees, and are still actively changing, possibly in backwards-incompatible ways.

rustfmt-preview for automatic code formatting

Minimum Rust version: 1.24

If you'd like to have your code automatically formatted, you can install this component:

$ rustup component add rustfmt-preview

This will install two tools, rustfmt and cargo-fmt, that will reformat your code for you! For example:

$ cargo fmt

will reformat your entire Cargo project.

rls-preview for IDE integration

Minimum Rust version: 1.21

Many IDE features are built off of the langserver protocol. To gain support for Rust with these IDEs, you'll need to install the Rust language sever, aka the "RLS":

$ rustup component add rls-preview

Your IDE should take it from there.

clippy-preview for more lints

For even more lints to help you write Rust code, you can install clippy:

$ rustup component add clippy-preview

This will install cargo-clippy for you:

$ cargo clippy

For more, check out clippy's documentation.

llvm-tools-preview for using extra LLVM tools

If you'd like to use the lld linker, or other tools like llvm-objdump or llvm-objcopy, you can install this component:

$ rustup component add llvm-tools-preview

This is the newest component, and so doesn't have good documentation at the moment.

Cargo and crates.io

In this chapter of the guide, we discuss a few improvements to cargo and crates.io. A notable addition here is the new cargo check command.

cargo check for faster checking

Minimum Rust version: 1.16

cargo check is a new subcommand should speed up the development workflow in many cases.

What does it do? Let's take a step back and talk about how rustc compiles your code. Compilation has many "passes", that is, there are many distinct steps that the compiler takes on the road from your source code to producing the final binary. However, you can think of this process in two big steps: first, rustc does all of its safety checks, makes sure your syntax is correct, all that stuff. Second, once it's satisfied that everything is in order, it produces the actual binary code that you end up executing.

It turns out that that second step takes a lot of time. And most of the time, it's not neccesary. That is, when you're working on some Rust code, many developers will get into a workflow like this:

  1. Write some code.
  2. Run cargo build to make sure it compiles.
  3. Repeat 1-2 as needed.
  4. Run cargo test to make sure your tests pass.
  5. Try the binary yourself
  6. GOTO 1.

In step two, you never actually run your code. You're looking for feedback from the compiler, not to actually run the binary. cargo check supports exactly this use-case: it runs all of the compiler's checks, but doesn't produce the final binary. To use it:

$ cargo check

where you may normally cargo build. The workflow now looks like:

  1. Write some code.
  2. Run cargo check to make sure it compiles.
  3. Repeat 1-2 as needed.
  4. Run cargo test to make sure your tests pass.
  5. Run cargo build to build a binary and try it yourself
  6. GOTO 1.

So how much speedup do you actually get? Like most performance related questions, the answer is "it depends." Here are some very un-scientific benchmarks at the time of writing.

build performance check performance speedup
initial compile 11s 5.6s 1.96x
second compile (no changes) 3s 1.9s 1.57x
third compile with small change 5.8s 3s 1.93x

cargo install for easy installation of tools

Minimum Rust version: 1.5

Cargo has grown a new install command. This is intended to be used for installing new subcommands for Cargo, or tools for Rust developers. This doesn't replace the need to build real, native packages for end-users on the platforms you support.

For example, this guide is created with mdbook. You can install it on your system with

$ cargo install mdbook

And then use it with

$ mdbook --help

As an example of extending Cargo, you can use the cargo-update package. To install it:

$ cargo install cargo-update

This will allow you to use this command, which checks everything you've cargo install'd and updates it to the latest version:

$ cargo install-update -a

cargo new defaults to a binary project

Minimum Rust version: 1.25

cargo new will now default to generating a binary, rather than a library. We try to keep Cargo’s CLI quite stable, but this change is important, and is unlikely to cause breakage.

For some background, cargo new accepts two flags: --lib, for creating libraries, and --bin, for creating binaries, or executables. If you don’t pass one of these flags, it used to default to --lib. At the time, we made this decision because each binary (often) depends on many libraries, and so we thought the library case would be more common. However, this is incorrect; each library is depended upon by many binaries. Furthermore, when getting started, what you often want is a program you can run and play around with. It’s not just new Rustaceans though; even very long-time community members have said that they find this default surprising. As such, we’ve changed it, and it now defaults to --bin.

cargo rustc for passing arbitrary flags to rustc

Minimum Rust version: 1.1

cargo rustc is a new subcommand for Cargo that allows you to pass arbitrary rustc flags through Cargo.

For example, Cargo does not have a way to pass unstable flags built-in. But if we'd like to use print-type-sizes to see what layout information our types have. We can run this:

$ cargo rustc -- -Z print-type-sizes

And we'll get a bunch of output describing the size of our types.

Note

cargo rustc only passes these flags to invocations of your crate, and not to any rustc invocations used to build dependencies. If you'd like to do that, see $RUSTFLAGS.

Cargo workspaces for multi-package projects

Minimum Rust version: 1.12

Cargo used to have two levels of organization:

  • A package contains one or more crates
  • A crate has one or more modules

Cargo now has an additional level:

  • A workspace contains one or more packages

This can be useful for larger projects. For example, the futures package is a workspace that contains many related packages:

  • futures
  • futures-util
  • futures-io
  • futures-channel

and more.

Workspaces allow these packages to be developed individually, but they share a single set of dependencies, and therefore have a single target directory and a single Cargo.lock.

For more details about workspaces, please see the Cargo documentation.

Multi-file examples

Minimum Rust version: 1.22

Cargo has an examples feature for showing people how to use your package. By putting individual files inside of the top-level examples directory, you can create multiple examples.

But what if your example is too big for a single file? Cargo supports adding sub-directories inside of examples, and looks for a main.rs inside of them to build the example. It looks like this:

my-package
 └──src
     └── lib.rs // code here
 └──examples 
     └── simple-example.rs // a single-file example
     └── complex-example
        └── helper.rs
        └── main.rs // a more complex example that also uses `helper` as a submodule

Replacing dependencies with patch

Minimum Rust version: 1.21

The [patch] section of your Cargo.toml can be used when you want to override certain parts of your dependency graph.

Cargo has a [replace] feature that is similar; while we don't intend to deprecate or remove [replace], you should prefer [patch] in all circumstances.

So what’s it look like? Let’s say we have a Cargo.toml that looks like this:

[dependencies]
foo = "1.2.3"

In addition, our foo package depends on a bar crate, and we find a bug in bar. To test this out, we’d download the source code for bar, and then update our Cargo.toml:

[dependencies]
foo = "1.2.3"

[patch.crates-io]
bar = { path = '/path/to/bar' }

Now, when you cargo build, it will use the local version of bar, rather than the one from crates.io that foo depends on. You can then try out your changes, and fix that bug!

For more details, see the documentation for patch.

이제 Cargo가 local registry replacement를 쓸수 있습니다!

Minimum Rust version: 1.12

Cargo finds its packages in a "source". The default source is crates.io. However, you can choose a different source in your .cargo/config:

[source.crates-io]
replace-with = 'my-awesome-registry'

[source.my-awesome-registry]
registry = 'https://github.com/my-awesome/registry-index'

This configuration means that instead of using crates.io, Cargo will query the my-awesome-registry source instead (configured to a different index here). This alternate source must be the exact same as the crates.io index. Cargo assumes that replacement sources are exact 1:1 mirrors in this respect, and the following support is designed around that assumption.

When generating a lock file for crate using a replacement registry, the original registry will be encoded into the lock file. For example in the configuration above, all lock files will still mention crates.io as the registry that packages originated from. This semantically represents how crates.io is the source of truth for all crates, and this is upheld because all replacements have a 1:1 correspondance.

Overall, this means that no matter what replacement source you're working with, you can ship your lock file to anyone else and you'll all still have verifiably reproducible builds!

This has enabled tools like cargo-vendor and cargo-local-registry, which are often useful for "offline builds." They prepare the list of all Rust dependencies ahead of time, which lets you ship them to a build machine with ease.

Crates.io disallows wildcard dependencies

Minimum Rust version: 1.6

Crates.io will not allow you to upload a package with a wildcard dependency. In other words, these:

[dependencies]
regex = "*"

A wildcard dependency means that you work with any possible version of your dependency. This is highly unlikely to be true, and would cause unnecessary breakage in the ecosystem.

Instead, depend on a version range. For example, ^ is the default, so you could use

[dependencies]
regex = "1.0.0"

instead. >, <=, and all of the other, non-* ranges work as well.

Documentation

이 챕터에서는, documentation에 관한 몇가지 개선점에 대해서 알아볼것입니다. 주목할 만한 사항은 The Rust Programming Language의 두번째 판입니다

"The Rust Programming Language"의 두번째 판

Minimum Rust version: 1.18 2판의 초고

Minimum Rust version: 1.26 2판의 최종본

Minimum Rust version: 1.28 2018버전의 초고

We've distributed a copy of "The Rust Programming Language," affectionately nicknamed "the book", with every version of Rust since Rust 1.0.

However, because it was written before Rust 1.0, it started showing its age. Many parts of the book are vague, because it was written before the true details were nailed down for the 1.0 release. It didn't do a fantastic job of teaching lifetimes.

Starting with Rust 1.18, we shipped drafts of a second edition of the book. The final version was shipped with Rust 1.26. The new edition is a complete re-write from the ground up, using the last two years of knowledge we’ve gained from teaching people Rust. You’ll find brand-new explanations for a lot of Rust’s core concepts, new projects to build, and all kinds of other good stuff. Please check it out and let us know what you think!

You can also purchase a dead-tree version from No Starch Press. Now that the print version has shipped, the second edition is frozen.

The names are a bit confusing though, because the "second edition" of the book is the first printed edition of the book. As such, we decided that newer editions of the book will correspond with newer editions of Rust itself, and so starting with 1.28, we've been shipping drafts of the next version, the 2018 Edition. It's still pretty close to the second edition, but contains information about newer features since the book's content was frozen. We'll be continuing to update this edition until we decide to print a second edition in paper.

러스트 책 선반

Minimum Rust version: various, each book is different.

As Rust's documentation has grown, we've gained far more than just "The book" and the reference. We now have a collection of various long-form docs, nicknamed "the Rust Bookshelf." Different resources are added at various times, and we're adding new ones as more get written.

The Cargo book

Minimum Rust version: 1.21

Historically, Cargo’s docs were hosted on http://doc.crates.io, which doesn’t follow the release train model, even though Cargo itself does. This led to situations where a feature would land in Cargo nightly, the docs would be updated, and then for up to twelve weeks, users would think that it should work, but it wouldn’t yet. https://doc.rust-lang.org/cargo is the new home of Cargo’s docs, and http://doc.crates.io now redirects there.

The rustdoc book

Minimum Rust version: 1.21

Rustdoc, our documentation tool, now has a guide at https://doc.rust-lang.org/rustdoc.

Rust By Example

Minimum Rust version: 1.25

Rust by Example used to live at https://rustbyexample.com, but now is part of the Bookshelf! It can be found at https://doc.rust-lang.org/rust-by-example/. RBE lets you learn Rust through short code examples and exercises, as opposed to the lengthy prose of The Book.

The Rustonomicon

Minimum Rust version: 1.3

We now have a draft book, The Rustonomicon: the Dark Arts of Advanced and Unsafe Rust Programming.

From the title, I'm sure you can guess: this book discusses some advanced topics, including unsafe. It's a must-read for anyone who's working at the lowest levels with Rust.

std::os has documentation for all platforms

Minimum Rust version: 1.21

The std::os module contains operating system specific functionality. You’ll now see more than just linux, the platform we build the documentation on.

We’ve long regretted that the hosted version of the documentation has been Linux-specific; this is a first step towards rectifying that. This is specific to the standard library and not for general use; we hope to improve this further in the future.

rustdoc

In this chapter of the guide, we discuss a few improvements to rustdoc. A notable addition to it was that documentation tests can now compile-fail.

Doc test가 이제 컴파일이 되는지 안되는지 테스트될 수 있습니다!

Minimum Rust version: 1.22

이제 여러분은 compile-fail테스트를 만들 수 있습니다. 이렇게요:

/// ```compile_fail
/// let x = 5;
/// x += 2; // shouldn't compile!
/// ```
# fn foo() {}

Please note that these kinds of tests can be more fragile than others, as additions to Rust may cause code to compile when it previously would not. Consider the first release with ?, for example: code using ? would fail to compile on Rust 1.21, but compile successfully on Rust 1.22, causing your test suite to start failing.

Rustdoc uses CommonMark

Minimum Rust version: 1.25 for support by default

Minimum Rust version: 1.23 for support via a flag

Rustdoc lets you write documentation comments in Markdown. At Rust 1.0, we were using the hoedown markdown implementation, written in C. Markdown is more of a family of implementations of an idea, and so hoedown had its own dialect, like many parsers. The CommonMark project has attempted to define a more strict version of Markdown, and so now, Rustdoc uses it by default.

As of Rust 1.23, we still defaulted to hoedown, but you could enable Commonmark via a flag, --enable-commonmark. Today, we only support CommonMark.

Platform and target support

In this chapter of the guide, we discuss a few improvements to platform and target support. A notable addition to it was that the libcore library now works on stable Rust.

libcore for low-level Rust

Minimum Rust version: 1.6

Rust’s standard library is two-tiered: there’s a small core library, libcore, and the full standard library, libstd, that builds on top of it. libcore is completely platform agnostic, and requires only a handful of external symbols to be defined. Rust’s libstd builds on top of libcore, adding support for things like memory allocation and I/O. Applications using Rust in the embedded space, as well as those writing operating systems, often eschew libstd, using only libcore.

As an additional note, while building libraries with libcore is supported today, building full applications is not yet stable.

To use libcore, add this flag to your crate root:

#![no_std]

This will remove the standard library, and bring the core crate into your namespace for use:

#![no_std]

use core::cell::Cell;

You can find libcore's documentation here.

WebAssembly support

Minimum Rust version: 1.14 for emscripten

Minimum Rust version: nightly for wasm32-unknown-unknown

Rust has gained support for WebAssembly, meaning that you can run Rust code in your browser, client-side.

In Rust 1.14, we gained support through emscripten. With it installed, you can write Rust code and have it produce asm.js (the precusor to wasm) and/or WebAssembly.

Here's an example of using this support:

$ rustup target add wasm32-unknown-emscripten
$ echo 'fn main() { println!("Hello, Emscripten!"); }' > hello.rs
$ rustc --target=wasm32-unknown-emscripten hello.rs
$ node hello.js

However, in the meantime, Rust has also grown its own support, independent from Emscripten. This is known as "the unknown target", because instead of wasm32-unknown-emscripten, it's wasm32-unknown-unknown. This will be the preferred target to use once it's ready, but for now, it's really only well-supported in nightly.

Global allocators

Minimum Rust version: 1.28

Allocators are the way that programs in Rust obtain memory from the system at runtime. Previously, Rust did not allow changing the way memory is obtained, which prevented some use cases. On some platforms, this meant using jemalloc, on others, the system allocator, but there was no way for users to control this key component. With 1.28.0, the #[global_allocator] attribute is now stable, which allows Rust programs to set their allocator to the system allocator, as well as define new allocators by implementing the GlobalAlloc trait.

The default allocator for Rust programs on some platforms is jemalloc. The standard library now provides a handle to the system allocator, which can be used to switch to the system allocator when desired, by declaring a static and marking it with the #[global_allocator] attribute.

use std::alloc::System;

#[global_allocator]
static GLOBAL: System = System;

fn main() {
    let mut v = Vec::new();
    // This will allocate memory using the system allocator.
    v.push(1);
}

However, sometimes you want to define a custom allocator for a given application domain. This is also relatively easy to do by implementing the GlobalAlloc trait. You can read more about how to do this in the documentation.

MSVC toolchain support

Minimum Rust version: 1.2

At the release of Rust 1.0, we only supported the GNU toolchain on Windows. With the release of Rust 1.2, we introduced initial support for the MSVC toolchain. After that, as support matured, we eventually made it the default choice for Windows users.

The difference between the two matters for interacting with C. If you're using a library built with one toolchain or another, you need to match that with the appropriate Rust toolchain. If you're not sure, go with MSVC; it's the default for good reason.

To use this feature, simply use Rust on Windows, and the installer will default to it. If you'd prefer to switch to the GNU toolchain, you can install it with Rustup:

$ rustup toolchain install stable-x86_64-pc-windows-gnu

MUSL support for fully static binaries

Minimum Rust version: 1.1

By default, Rust will statically link all Rust code. However, if you use the standard library, it will dynamically link to the system's libc implementation.

If you'd like a 100% static binary, the MUSL libc can be used on Linux.

Installing MUSL support

To add support for MUSL, you need to choose the correct target. The forge has a full list of targets supported, with a number of ones using musl.

If you're not sure what you want, it's probably x86_64-unknown-linux-musl, for 64-bit Linux. We'll be using this target in this guide, but the instructions remain the same for other targets, just change the name wherever we mention the target.

To get support for this target, you use rustup:

$ rustup target add x86_64-unknown-linux-musl

This will install support for the default toolchain; to install for other toolchains, add the --toolchain flag. For example:

$ rustup target add x86_64-unknown-linux-musl --toolchain=nightly

Building with MUSL

To use this new target, pass the --target flag to Cargo:

$ cargo build --target x86_64-unknown-linux-musl

The binary produced will now be built with MUSL!

cdylib crates for C interoperability

Minimum Rust version: 1.10 for rustc

Minimum Rust version: 1.11 for cargo

If you're producing a library that you intend to be used from C (or another language through a C FFI), there's no need for Rust to include Rust-specific stuff in the final object code. For libraries like that, you'll want to use the cdylib crate type in your Cargo.toml:

[lib]
crate-type = ["cdylib"]

This will produce a smaller binary, with no Rust-specific information inside of it.

Unstable feature status

Language

Feature Status Minimum Edition
impl Trait Shipped, 1.26 2015
Basic slice patterns Shipped, 1.26 2015
Default match bindings Shipped, 1.26 2015
Anonymous lifetimes Shipped, 1.26 2015
dyn Trait Shipped, 1.27 2015
SIMD support Shipped, 1.27 2015
? in main/tests Shipping, 1.26 and 1.28 2015
In-band lifetimes Unstable; tracking issue 2015
Lifetime elision in impls Unstable; tracking issue 2015
Non-lexical lifetimes Implemented but not ready for preview 2015
T: 'a inference in structs Unstable; tracking issue 2015
Raw identifiers Shipping, 1.30; tracking issue 2015
Import macros via use Unstable; tracking issue ?
Module system path changes Unstable; tracking issue 2018

While some of these features are already available in Rust 2015, they are tracked here because they are being promoted as part of the Rust 2018 edition. Accordingly, they will be discussed in subsequent sections of this guide book. The features marked as "Shipped" are all available today in stable Rust, so you can start using them right now!

Standard library

Feature Status
Custom global allocators Will ship in 1.28

Tooling

Tool Status
RLS 1.0 Feature-complete; see 1.0 milestone
rustfmt 1.0 Finalizing spec; 1.0 milestone, style guide RFC, stability RFC
Clippy 1.0 RFC

Documentation

Tool Status
Edition Guide Initial draft complete
TRPL Updated as features stabilize

Web site

The visual design is being finalized, and early rounds of content brainstorming are complete.