mirror of https://github.com/rust-lang/nomicon
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
115 lines
9.8 KiB
115 lines
9.8 KiB
# 다른 데이터 표현들
|
|
|
|
러스트는 기본으로부터 다른 데이터 설계 전략을 구성하게 해 줍니다.
|
|
|
|
[불안전 코드 가이드라인][unsafe_guide]도 있습니다 (**비표준이니** 주의하세요).
|
|
|
|
## repr(C)
|
|
|
|
이것은 가장 중요한 `repr`입니다. 이것은 매우 간단한 의도를 가지고 있습니다: C가 하는대로 하라는 것이죠. 필드들의 정렬 순서, 크기, 정렬선은 C나 C++에서 되는 것 같이 될 겁니다.
|
|
어떤 타입이든 FFI 경계를 넘겨 보내려면 `repr(C)`로 표현되어야 하는데, 이는 C가 프로그래밍 세계의 공용어이기 때문입니다. 이것은 값을 다른 타입으로 재해석하는 것과 같은, 데이터 레이아웃을 가지고 정교한 장난을 수월하게 칠 수 있기 위해서 필수적입니다.
|
|
|
|
우리는 [rust-bindgen]과 [cbindgen]를 둘 다, 혹은 둘 중 하나를 써서 당신 대신 FFI 경계를 관리하기를 매우 권장합니다. 러스트 팀은 이 프로젝트들과 긴밀하게 작업하여 이들이 튼튼하게 작동하고,
|
|
타입 레이아웃과 `repr`들에 대한 현재와 미래의 보장에 잘 맞도록 신경쓰고 있습니다.
|
|
|
|
`repr(C)`와 러스트의 (C보다) 이상한 데이터 설계 기능의 상호작용은 주의해야 합니다. "FFI를 위한" 것과 "데이터 표현을 바꾸기" 위한 두 가지 목적이 동시에 있기 때문에, `repr(C)`는 FFI 경계로 보내면 말이 안되거나 문제가 생길 수 있는 타입들에 적용할 수 있습니다.
|
|
|
|
* 무량 타입(ZST)은 그대로 크기가 0으로 되는데, 이것은 C에서 표준 동작이 아니고, C++에서 빈 타입의 동작과 분명하게 반대되는데, C++에서는 빈 타입이라도 한 바이트의 공간을 차지해야 한다고 말하기 때문입니다.
|
|
|
|
* 동량 타입(DST)의 포인터(넓은 포인터)와 튜플은 C에서 없는 개념이므로, FFI로 보내면 절대 안전하지 않습니다.
|
|
|
|
* 필드가 있는 열거형 또한 C와 C++에서 없는 개념이지만, 타입 사이의 유효한 변환이 [정의되어 있습니다][really-tagged].
|
|
|
|
* 만약 `T`가 [FFI로 보내도 안전하고 널이 아닌 포인터 타입이라면](ffi.html#the-nullable-pointer-optimization), `Option<T>`는 `T`와 같은 데이터 표현과 ABI를 갖추고, 따라서 FFI로 보내도 안전하다는 것이 보장됩니다. 이 글을 쓰는 시점에서, 이것은 `&`, `&mut`, 그리고 함수 포인터들에 해당하는데, 이것들은 전부 널이 될 수 없기 때문입니다.
|
|
|
|
* 튜플 구조체는 `repr(C)`에서는 일반 구조체와 같은데, 일반 구조체와 다른 점은 필드의 이름이 없다는 것뿐이기 때문입니다.
|
|
|
|
* 필드가 없는 열거형에 있어서는 `repr(C)`는 `repr(u*)` (다음 섹션을 보세요) 중 하나와 같습니다. 여기서 선택되는 바이트 크기는 타겟 플랫폼의 C 애플리케이션 이진 인터페이스(ABI)에서의 기본 열거형 크기입니다. 주의할 점은 C에서의 데이터 표현은 구현에 따라 다르게 정의되어 있으므로, 이것은 "최선의 추측"이라는 점입니다. 특별히, 관련된 C 코드가 특정한 플래그로 컴파일되면 이 설명이 맞지 않을 수도 있습니다.
|
|
|
|
* `repr(C)`나 `repr(u*)`로 표현되는 필드 없는 열거형은, C나 C++에서는 허용되는 동작이지만, 그래도 대응하는 형이 없는 정수 값으로 설정하면 안됩니다. 열거형의 형이 대응하지 않는 열거형의 값을 (불안전하게) 만들어내는 것은 미정의 동작입니다. (이렇게 함으로써 패턴 완전 매칭이 잘 작성되고 컴파일되게 됩니다.)
|
|
|
|
## repr(transparent)
|
|
|
|
`#[repr(transparent)]`은 크기가 0이 아닌 하나의 필드를 가지고 있는 (무량 필드는 더 있어도 됩니다) 구조체나 형이 하나인 열거형에만 쓰일 수 있습니다.
|
|
이것의 효과는 구조체/열거형의 전체 레이아웃과 ABI가 그 하나의 필드와 완전히 동일하도록 보장된다는 것입니다.
|
|
|
|
> 주의: `repr(transparent)`를 공용체에 적용하는 `transparent_unions` nightly 기능이 있지만,
|
|
> 디자인 문제 때문에 표준화되지 않았습니다. 더 자세한 내용은 [이슈][issue-60405]를 참고하세요.
|
|
|
|
이것의 목표는 구조체/열거형과 그의 유일한 필드 간에 변환을 가능하게 하는 것입니다. 이것의 한 예는 [`UnsafeCell`]인데, 이것은 자신이 감싸고 있는 타입으로 변환될 수 있습니다 ([`UnsafeCell`]은 또한 불안정한 [no_niche][no-niche-pull]를 쓰기 때문에, 이것의 ABI는 다른 타입 속에 들어갔을 때에 동일하다고 보장되지는 않습니다).
|
|
|
|
또, 그 유일한 필드가 FFI로 보내도 괜찮은 타입이라면, 그 유일한 필드가 있는 구조체/열거형을 FFI로 보내는 작업도 잘 작동하는 것이 보장됩니다. 예를 들어, 이것은 `struct Foo(f32)`나 `enum Foo { Bar(f32) }`가 `f32`와 항상 동일한 ABI를 가지게 하도록 하기 위해서 꼭 필요합니다.
|
|
|
|
이 표현 방식은 타입의 유일한 필드가 `pub`이거나, 그 레이아웃이 단순하게 묘사되었을 경우에만 공개 ABI의 한 부분으로 인정됩니다. 그렇지 않다면 이 레이아웃은 다른 크레이트들이 의존해서는 안됩니다.
|
|
|
|
더 자세한 내용은 [RFC 1758][rfc-transparent]과 [RFC 2645][rfc-transparent-unions-enums]에 있습니다.
|
|
|
|
## repr(u*), repr(i*)
|
|
|
|
이것들은 필드 없는 열거형을 만들기 위한 크기와 부호를 지정합니다. 식별자가 이 정수를 벗어나서 오버플로우되면 컴파일할 때 에러가 발생할 겁니다. 하지만 이것을 러스트에서 허용할 수도 있습니다: 오버플로우된 순간 0이 되게 명시적으로 말하는 것이죠.
|
|
그러나 러스트는 열거형의 두 개의 형이 같은 식별자를 가지는 것을 허용하지는 않을 겁니다.
|
|
|
|
"필드 없는 열거형"이라는 용어는 단지 열거형이 그 형에서 데이터를 가지지 않는다는 것을 말합니다. `repr(u*)`나 `repr(C)`가 없는 필드 없는 열거형은 여전히 러스트 타입이고, 안정적인 ABI 표현이 존재하지 않습니다.
|
|
`repr`을 더하는 것은 ABI를 위해 이 열거형이 지정된 타입의 정수로 다뤄지게 합니다.
|
|
|
|
만약 열거형이 필드가 있다면, 타입의 정해진 레이아웃이 있다는 점에서 효과는 `repr(C)`의 효과와 비슷하게 됩니다. 이것은 열거형을 C 코드에 넘기고, 그 타입의 실제 표현을 접근하고 직접 그 태그와 필드를 조작할 수 있게 해 줍니다.
|
|
자세한 내용은 [RFC][really-tagged]를 참고하세요.
|
|
|
|
이 `repr`은 구조체에는 아무 효과도 없습니다.
|
|
|
|
필드가 있는 열거형에 명시적인 `repr(u*)`, `repr(i*)`, 혹은 `repr(C)`를 추가하는 것은 널 포인터 최적화를 억제하는데, 이것은 다음과 같습니다:
|
|
|
|
```rust
|
|
# use std::mem::size_of;
|
|
enum MyOption<T> {
|
|
Some(T),
|
|
None,
|
|
}
|
|
|
|
#[repr(u8)]
|
|
enum MyReprOption<T> {
|
|
Some(T),
|
|
None,
|
|
}
|
|
|
|
assert_eq!(8, size_of::<MyOption<&u16>>());
|
|
assert_eq!(16, size_of::<MyReprOption<&u16>>());
|
|
```
|
|
이 최적화는 필드가 없는 열거형에 명시적으로 `repr(u*)`, `repr(i*)`, 혹은 `repr(C)`가 적용된 경우에는 여전히 작동합니다.
|
|
|
|
## repr(packed)
|
|
|
|
`repr(packed)`는 타입에 어떤 여백도 제거하고, 한 바이트에 정렬하도록 러스트에 강제합니다. 이것은 메모리 사용량은 줄여주지만, 아마 다른 부작용들을 초래할 것입니다.
|
|
|
|
더 자세히 말하자면, 대부분의 아키텍쳐는 값들이 제대로 정렬되는 것을 *매우* 선호합니다. 이것은 제대로 정렬되지 않은 읽기는 뒤로 미뤄지거나 (x86), 심지어는 강제종료됩니다 (일부의 ARM 칩들).
|
|
이렇게 "압축"된 필드를 직접 읽거나 저장하는 간단한 경우들에서는, 컴파일러가 쉬프트나 마스크 같은 것들로 정렬 문제를 간단히 수정할 수 있을지도 모릅니다.
|
|
그러나 그 압축된 필드에 레퍼런스를 단다면, 컴파일러가 정렬되지 않은 읽기를 피할 수 있는 코드를 낼 수 있을 것 같지는 않습니다.
|
|
|
|
|
|
|
|
[이것이 미정의 동작을 일으킬 수 있기 때문에][ub_loads], 린트가 구현되었고 지금은 아주 심각한 오류가 될 것입니다.
|
|
|
|
`repr(packed)`는 가볍게 사용되어서는 안됩니다. 극심하게 이것이 필요하지 않다면 이것은 쓰여져서는 안됩니다.
|
|
|
|
이 표현은 `repr(C)`나 `repr(Rust)`와 함께 쓸 수 있습니다.
|
|
|
|
## repr(align(n))
|
|
|
|
`repr(align(n))`은 (`n`은 2의 거듭제곱입니다) 타입이 *최소* n의 정렬선을 가지도록 요구합니다.
|
|
|
|
이것은 여러 가지 장난을 가능하게 해 주는데, 예를 들어 배열의 이웃하는 원소들이 서로의 캐시 선을 절대 공유하지 않도록 하는 것이 있습니다 (이것으로 어떤 종류의 동시성 코드는 빠르게 할 수 있습니다).
|
|
|
|
이 표현은 `repr(C)`나 `repr(Rust)`와 함께 쓸 수 있지만, `repr(packed)`와는 함께 쓸 수 없습니다.
|
|
|
|
[unsafe_guide]: https://rust-lang.github.io/unsafe-code-guidelines/layout.html
|
|
[drop_flags]: drop-flags.html
|
|
[ub_loads]: https://github.com/rust-lang/rust/issues/27060
|
|
[issue-60405]: https://github.com/rust-lang/rust/issues/60405
|
|
[`UnsafeCell`]: https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html
|
|
[rfc-transparent]: https://github.com/rust-lang/rfcs/blob/master/text/1758-repr-transparent.md
|
|
[rfc-transparent-unions-enums]: https://rust-lang.github.io/rfcs/2645-transparent-unions.html
|
|
[really-tagged]: https://github.com/rust-lang/rfcs/blob/master/text/2195-really-tagged-unions.md
|
|
[rust-bindgen]: https://rust-lang.github.io/rust-bindgen/
|
|
[cbindgen]: https://github.com/eqrion/cbindgen
|
|
[no-niche-pull]: https://github.com/rust-lang/rust/pull/68491
|