러스트는 동량(動量) 타입(DST)을 지원합니다: 정적으로 알려진 크기나 정렬선이 없는 타입을 말이죠. 표면적으로는 이것은 좀 말이 되지 않습니다: 러스트는 무언가와 올바르게 작업하기 위해서는 그것의 크기와 정렬선을 *알아야 하거든요!*
이런 면에서 DST는 보통의 타입이 아닙니다. 정적으로 알려진 크기가 없기 때문에, 이런 타입들은 포인터 뒤에서만 존재할 수 있습니다.
러스트는 동량(動量) 타입(DST)을 지원합니다: 정적으로 알려진 크기나 정렬선이 없는 타입을 말이죠. 표면적으로는 이것은 좀 말이 되지 않습니다: 러스트는 무언가와 올바르게 작업하기 위해서는 그것의 크기와 정렬선을 *알아야 하거든요!*
이런 면에서 DST는 보통의 타입이 아닙니다. 정적으로 알려진 크기가 없기 때문에, 이런 타입들은 포인터 뒤에서만 존재할 수 있습니다.
따라서 DST를 가리키는 포인터는 포인터와 DST를 "완성하는" 정보로 이루어진 *넓은* 포인터가 됩니다 (밑에서 더 설명합니다).
언어에서 보이는 주요한 DST는 두 가지가 있습니다:
언어에서 보이는 주요한 DST는 두 가지가 있습니다:
* 트레잇 객체: `dyn MyTrait`
* 슬라이스: [`[T]`][slice], [`str`], 등등
트레잇 객체는 그것이 특정하는 트레잇을 구현하는 어떤 타입을 표현합니다. 정확한 원래 타입은 런타임 리플렉션을 위해 *지워지고,* 타입을 쓰기 위해 필요한 모든 정보를 담고 있는 vtable로 대체됩니다. 트레잇 객체를 완성하는 정보는 이 vtable의 포인터입니다. 포인터가 가리키는 대상의 런타임 크기는 vtable에서 동적으로 요청될 수 있습니다.
트레잇 객체는 그것이 특정하는 트레잇을 구현하는 어떤 타입을 표현합니다. 정확한 원래 타입은 런타임 리플렉션을 위해 *지워지고,* 타입을 쓰기 위해 필요한 모든 정보를 담고 있는 vtable로 대체됩니다. 트레잇 객체를 완성하는 정보는 이 vtable의 포인터입니다. 포인터가 가리키는 대상의 런타임 크기는 vtable에서 동적으로 요청될 수 있습니다.
슬라이스는 어떤 연속적인 저장소에 대한 뷰일 뿐입니다 -- 보통 이 저장소는 배열이거나 `Vec`입니다. 슬라이스 포인터를 완성시키는 정보는 가리키고 있는 원소들의 갯수입니다.
슬라이스는 어떤 연속적인 저장소에 대한 뷰일 뿐입니다 -- 보통 이 저장소는 배열이거나 `Vec`입니다. 슬라이스 포인터를 완성시키는 정보는 가리키고 있는 원소들의 갯수입니다.
가리키는 대상의 런타임 크기는 그냥 한 원소의 정적으로 알려진 크기와 원소들의 갯수를 곱한 것입니다.
구조체는 사실 마지막 필드로써 하나의 동량 타입을 직접 저장할 수 있지만, 그러면 그들 자신도 동량 타입이 됩니다:
구조체는 사실 마지막 필드로써 하나의 동량 타입을 직접 저장할 수 있지만, 그러면 그들 자신도 동량 타입이 됩니다:
```rust
// 직접적으로 스택에 저장할 수 없음
@ -28,7 +28,7 @@ struct MySuperSlice {
}
```
이런 타입은 생성할 방법이 없으면 별로 쓸모가 없지만 말이죠. 현재 유일하게 제대로 지원되는, 커스텀 동량 타입을 만들 방법은 타입을 제네릭으로 만들고 *크기 강제 망각*을 실행하는 것입니다:
이런 타입은 생성할 방법이 없으면 별로 쓸모가 없지만 말이죠. 현재 유일하게 제대로 지원되는, 커스텀 동량 타입을 만들 방법은 타입을 제네릭으로 만들고 *크기 강제 망각*을 실행하는 것입니다:
```rust
struct MySuperSliceable<T:?Sized> {
@ -53,7 +53,7 @@ fn main() {
## 무량(無量) 타입 (ZST)
러스트는 타입이 공간을 차지하지 않는다고 말하는 것도 허용합니다:
러스트는 타입이 공간을 차지하지 않는다고 말하는 것도 허용합니다:
```rust
struct Nothing; // 필드 없음 = 크기 없음
@ -66,15 +66,15 @@ struct LotsOfNothing {
}
```
무량(無量) 타입(ZST)은, 당연하게도 그 자체로는, 별로 쓸모가 없습니다. 하지만 러스트의 많은 기이한 레이아웃 선택들이 그렇듯이, 그들의 잠재력은 일반적인 환경에서 빛나게 됩니다:
러스트는 무량 타입의 값을 생성하거나 저장하는 모든 작업이 아무 작업도 하지 않는 것과 같을 수 있다는 사실을 매우 이해하거든요. 일단 값을 저장한다는 것부터가 말이 안됩니다 -- 차지하는 공간도 없는걸요.
무량(無量) 타입(ZST)은, 당연하게도 그 자체로는, 별로 쓸모가 없습니다. 하지만 러스트의 많은 기이한 레이아웃 선택들이 그렇듯이, 그들의 잠재력은 일반적인 환경에서 빛나게 됩니다:
러스트는 무량 타입의 값을 생성하거나 저장하는 모든 작업이 아무 작업도 하지 않는 것과 같을 수 있다는 사실을 매우 이해하거든요. 일단 값을 저장한다는 것부터가 말이 안됩니다 -- 차지하는 공간도 없는걸요.
또 그 타입의 값은 오직 하나이므로, 어떤 값이 읽히든 그냥 무에서 값을 만들어내면 됩니다 -- 이것 또한 차지하는 공간이 없기 때문에, 아무것도 하지 않는 것과 같습니다.
이것의 가장 극단적인 예시 중 하나가 `Map`과 `Set`입니다. `Map<Key, Value>`가 주어졌을 때, `Set<Key>`를 `Map<Key, UselessJunk>`를 적당히 감싸는 자료구조로 만드는 것은 흔하게 볼 수 있습니다.
많은 언어들에서 이것은 `UselessJunk` 타입을 위한 공간을 할당하고, `UselessJunk`를 가지고 아무것도 하지 않기 위해서 그 값을 저장하고 읽는 작업을 강제할 겁니다.
이것의 가장 극단적인 예시 중 하나가 `Map`과 `Set`입니다. `Map<Key, Value>`가 주어졌을 때, `Set<Key>`를 `Map<Key, UselessJunk>`를 적당히 감싸는 자료구조로 만드는 것은 흔하게 볼 수 있습니다.
많은 언어들에서 이것은 `UselessJunk` 타입을 위한 공간을 할당하고, `UselessJunk`를 가지고 아무것도 하지 않기 위해서 그 값을 저장하고 읽는 작업을 강제할 겁니다.
이 작업이 불필요하다는 것을 증명하려면 컴파일러는 복잡한 분석을 해야 할 겁니다.
그러나 러스트에서는 우리는 그냥 `Set<Key> = Map<Key, ()>`라고 말할 수 있습니다. 이제 러스트는 컴파일할 때 모든 메모리 읽기와 저장은 의미가 없고, 저장 공간을 할당할 필요도 없다는 것을 알게 됩니다.
그러나 러스트에서는 우리는 그냥 `Set<Key> = Map<Key, ()>`라고 말할 수 있습니다. 이제 러스트는 컴파일할 때 모든 메모리 읽기와 저장은 의미가 없고, 저장 공간을 할당할 필요도 없다는 것을 알게 됩니다.
결과적으로 나오는 코드는 그냥 `HashSet`의 커스텀 구현일 뿐이고, `HashMap`이 값을 처리할 때의 연산은 존재하지 않게 됩니다.
안전한 코드는 무량 타입에 대해서 걱정하지 않아도 되지만, *불안전한* 코드는 크기가 없는 타입의 중요성을 신경써야 합니다. 특히 포인터 오프셋은 아무 작업도 하지 않는 것과 같고, 할당자는 보통 [0이 아닌 크기를 요구합니다][alloc].
@ -86,22 +86,22 @@ struct LotsOfNothing {
## 빈 타입
러스트는 또한 *그 타입의 값을 만들 수조차 없는* 타입을 정의하는 것도 지원합니다. 이런 타입들은 타입 측면에서만 말할 수 있고, 값 측면에서는 절대 말할 수 없습니다.
빈 타입은 형이 없는 열거형을 정의함으로써 만들 수 있습니다:
러스트는 또한 *그 타입의 값을 만들 수조차 없는* 타입을 정의하는 것도 지원합니다. 이런 타입들은 타입 측면에서만 말할 수 있고, 값 측면에서는 절대 말할 수 없습니다.
빈 타입은 형이 없는 열거형을 정의함으로써 만들 수 있습니다:
```rust
enum Void {} // 형 없음 = 비어 있음
```
빈 타입은 무량 타입보다도 더 작습니다. 빈 타입의 예시로 들 만한 것은 타입 측면에서의 접근불가성입니다. 예를 들어, 어떤 API가 일반적으로 `Result`를 반환해야 하지만, 어떤 경우에서는 실패할 수 없다고 합시다.
우리는 이것을 `Result<T, Void>`를 반환함으로써 타입 레벨에서 소통할 수 있습니다.
빈 타입은 무량 타입보다도 더 작습니다. 빈 타입의 예시로 들 만한 것은 타입 측면에서의 접근불가성입니다. 예를 들어, 어떤 API가 일반적으로 `Result`를 반환해야 하지만, 어떤 경우에서는 실패할 수 없다고 합시다.
우리는 이것을 `Result<T, Void>`를 반환함으로써 타입 레벨에서 소통할 수 있습니다.
API의 사용자들은 이런 `Result`의 값이 `Err`가 되기에 *정적으로 불가능하다는 것을* 알고 자신 있게 `unwrap`할 수 있을 겁니다, 왜냐하면 `Err` 값이 있으려면 `Void` 타입의 값이 생산되어야 하거든요.
원칙적으로는 러스트가 이런 사실에 기반하여 몇 가지 흥미로운 분석과 최적화를 수행할 수 있을 겁니다. 예를 들어 `Result<T, Void>`는 `Err` 형이 실제로 존재하지 않기에, 그냥 `T`로 표현할 수 있겠죠
원칙적으로는 러스트가 이런 사실에 기반하여 몇 가지 흥미로운 분석과 최적화를 수행할 수 있을 겁니다. 예를 들어 `Result<T, Void>`는 `Err` 형이 실제로 존재하지 않기에, 그냥 `T`로 표현할 수 있겠죠
(엄격하게 말하면, 이것은 보장되지 않은 최적화일 뿐이고, `T`와 `Result<T, Void>` 중 하나를 다른 하나로 변질시키는 것은 아직 미정의 동작입니다).
다음의 코드도 컴파일 *될 수도 있을* 겁니다:
다음의 코드도 컴파일 됩니다:
```rust,compile_fail
```rust
enum Void {}
let res: Result<u32,Void> = Ok(0);
@ -110,11 +110,9 @@ let res: Result<u32, Void> = Ok(0);
let Ok(num) = res;
```
하지만 아직 이 꼼수는 통하지 않습니다.
빈 타입에 대한 마지막 하나의 조그만 사실은, 빈 타입을 가리키는 생 포인터는 놀랍게도 유효하게 생성할 수 있지만, 그것을 역참조하는 것은 말이 안되기 때문에 미정의 동작이라는 것입니다.
우리는 C의 `void*` 타입을 `*const Void`로 설계하는 것을 추천하지 않습니다. 많은 사람들이 이렇게 했지만 얼마 지나지 않아 문제에 부딪혔는데, 러스트는 불안전한 코드로 빈 타입의 값을 만드려고 하는 것을 막는 안전 장치가 없고,
우리는 C의 `void*` 타입을 `*const Void`로 설계하는 것을 추천하지 않습니다. 많은 사람들이 이렇게 했지만 얼마 지나지 않아 문제에 부딪혔는데, 러스트는 불안전한 코드로 빈 타입의 값을 만드려고 하는 것을 막는 안전 장치가 없고,
만약 빈 타입의 값을 만들면, 그것은 미정의 동작이기 때문입니다. 이것은 특별히 문제가 되었는데, 개발자들이 생 포인터를 레퍼런스로 바꾸는 습관이 있었고 `&Void` 값을 만드는 것 *역시* 미정의 동작이기 때문입니다.
`*const ()` (혹은 비슷한 타입)은 `void*`에 대응해서 무리 없이 잘 동작하고, 안전성의 문제 없이 레퍼런스로 만들 수 있습니다.
@ -122,7 +120,7 @@ let Ok(num) = res;
## 외래 타입
*외래 타입*으로 불리는, 알 수 없는 크기의 타입을 추가하여 러스트 개발자들이 C의 `void*`나 다른 "선언되었지만 정의되지 않은" 타입들을 좀더 정확하게 설계하자는, [승인된 RFC가][extern-types] 있습니다.
*외래 타입*으로 불리는, 알 수 없는 크기의 타입을 추가하여 러스트 개발자들이 C의 `void*`나 다른 "선언되었지만 정의되지 않은" 타입들을 좀더 정확하게 설계하자는, [승인된 RFC가][extern-types] 있습니다.
하지만 러스트 2018 기준으로, [이 기능은 `size_of_val::<MyExternType>()`이 어떻게 작동해야 하는지에 걸려서 대기 상태에 갇혀 있습니다][extern-types-issue].
[불안전 코드 가이드라인][unsafe_guide]도 있습니다 (**비표준이니** 주의하세요).
## repr(C)
이것은 가장 중요한 `repr`입니다. 이것은 매우 간단한 의도를 가지고 있습니다: C가 하는대로 하라는 것이죠. 필드들의 정렬 순서, 크기, 정렬선은 C나 C++에서 되는 것 같이 될 겁니다.
어떤 타입이든 FFI 경계를 넘겨 보내려면 `repr(C)`로 표현되어야 하는데, 이는 C가 프로그래밍 세계의 공용어이기 때문입니다. 이것은 값을 다른 타입으로 재해석하는 것과 같은, 데이터 레이아웃을 가지고 정교한 장난을 수월하게 칠 수 있기 위해서 필수적입니다.
이것은 가장 중요한 `repr`입니다. 이것은 매우 간단한 의도를 가지고 있습니다: C가 하는대로 하라는 것이죠. 필드들의 정렬 순서, 크기, 정렬선은 C나 C++에서 되는 것 같이 될 겁니다.
어떤 타입이든 FFI 경계를 넘겨 보내려면 `repr(C)`로 표현되어야 하는데, 이는 C가 프로그래밍 세계의 공용어이기 때문입니다. 이것은 값을 다른 타입으로 재해석하는 것과 같은, 데이터 레이아웃을 가지고 정교한 장난을 수월하게 칠 수 있기 위해서 필수적입니다.
우리는 [rust-bindgen]과 [cbindgen]를 둘 다, 혹은 둘 중 하나를 써서 당신 대신 FFI 경계를 관리하기를 매우 권장합니다. 러스트 팀은 이 프로젝트들과 긴밀하게 작업하여 이들이 튼튼하게 작동하고,
우리는 [rust-bindgen]과 [cbindgen]를 둘 다, 혹은 둘 중 하나를 써서 당신 대신 FFI 경계를 관리하기를 매우 권장합니다. 러스트 팀은 이 프로젝트들과 긴밀하게 작업하여 이들이 튼튼하게 작동하고,
타입 레이아웃과 `repr`들에 대한 현재와 미래의 보장에 잘 맞도록 신경쓰고 있습니다.
`repr(C)`와 러스트의 (C보다) 이상한 데이터 설계 기능의 상호작용은 주의해야 합니다. "FFI를 위한" 것과 "데이터 표현을 바꾸기" 위한 두 가지 목적이 동시에 있기 때문에, `repr(C)`는 FFI 경계로 보내면 말이 안되거나 문제가 생길 수 있는 타입들에 적용할 수 있습니다.
@ -30,7 +30,7 @@
## repr(transparent)
`#[repr(transparent)]`은 크기가 0이 아닌 하나의 필드를 가지고 있는 (무량 필드는 더 있어도 됩니다) 구조체나 형이 하나인 열거형에만 쓰일 수 있습니다.
`#[repr(transparent)]`은 크기가 0이 아닌 하나의 필드를 가지고 있는 (무량 필드는 더 있어도 됩니다) 구조체나 형이 하나인 열거형에만 쓰일 수 있습니다.
이것의 효과는 구조체/열거형의 전체 레이아웃과 ABI가 그 하나의 필드와 완전히 동일하도록 보장된다는 것입니다.
> 주의: `repr(transparent)`를 공용체에 적용하는 `transparent_unions` nightly 기능이 있지만,
@ -46,13 +46,13 @@
## repr(u*), repr(i*)
이것들은 필드 없는 열거형을 만들기 위한 크기를 지정합니다. 식별자가 이 크기를 벗어나서 오버플로우되면 컴파일할 때 에러가 발생할 겁니다. 하지만 이것을 러스트에서 허용할 수도 있습니다: 오버플로우된 순간 0이 되게 명시적으로 말하는 것이죠.
그러나 러스트는 열거형의 두 개의 형이 같은 식별자를 가지는 것을 허용하지 않을 겁니다.
이것들은 필드 없는 열거형을 만들기 위한 크기와 부호를 지정합니다. 식별자가 이 정수를 벗어나서 오버플로우되면 컴파일할 때 에러가 발생할 겁니다. 하지만 이것을 러스트에서 허용할 수도 있습니다: 오버플로우된 순간 0이 되게 명시적으로 말하는 것이죠.
그러나 러스트는 열거형의 두 개의 형이 같은 식별자를 가지는 것을 허용하지는 않을 겁니다.
"필드 없는 열거형"이라는 용어는 단지 열거형이 그 형에서 데이터를 가지지 않는다는 것을 말합니다. `repr(u*)`나 `repr(C)`가 없는 필드 없는 열거형은 여전히 러스트 타입이고, 안정적인 ABI 표현이 존재하지 않습니다.
`repr`을 더하는 것은 ABI를 위해 이 열거형이 지정된 크기의 정수로 다뤄지게 합니다.
"필드 없는 열거형"이라는 용어는 단지 열거형이 그 형에서 데이터를 가지지 않는다는 것을 말합니다. `repr(u*)`나 `repr(C)`가 없는 필드 없는 열거형은 여전히 러스트 타입이고, 안정적인 ABI 표현이 존재하지 않습니다.
`repr`을 더하는 것은 ABI를 위해 이 열거형이 지정된 타입의 정수로 다뤄지게 합니다.
만약 열거형이 필드가 있다면, 타입의 정해진 레이아웃이 있다는 점에서 효과는 `repr(C)`의 효과와 비슷하게 됩니다. 이것은 열거형을 C 코드에 넘기고, 그 타입의 실제 표현을 접근하고 직접 그 태그와 필드를 조작할 수 있게 해 줍니다.
만약 열거형이 필드가 있다면, 타입의 정해진 레이아웃이 있다는 점에서 효과는 `repr(C)`의 효과와 비슷하게 됩니다. 이것은 열거형을 C 코드에 넘기고, 그 타입의 실제 표현을 접근하고 직접 그 태그와 필드를 조작할 수 있게 해 줍니다.