다른 데이터 표현들
러스트는 기본으로부터 다른 데이터 설계 전략을 구성하게 해 줍니다.
불안전 코드 가이드라인도 있습니다 (비표준이니 주의하세요).
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++에서 없는 개념이지만, 타입 사이의 유효한 변환이 정의되어 있습니다.
-
만약
T
가 FFI로 보내도 안전하고 널이 아닌 포인터 타입이라면,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 기능이 있지만, 디자인 문제 때문에 표준화되지 않았습니다. 더 자세한 내용은 이슈를 참고하세요.
이것의 목표는 구조체/열거형과 그의 유일한 필드 간에 변환을 가능하게 하는 것입니다. 이것의 한 예는 UnsafeCell
인데, 이것은 자신이 감싸고 있는 타입으로 변환될 수 있습니다 (UnsafeCell
은 또한 불안정한 no_niche를 쓰기 때문에, 이것의 ABI는 다른 타입 속에 들어갔을 때에 동일하다고 보장되지는 않습니다).
또, 그 유일한 필드가 FFI로 보내도 괜찮은 타입이라면, 그 유일한 필드가 있는 구조체/열거형을 FFI로 보내는 작업도 잘 작동하는 것이 보장됩니다. 예를 들어, 이것은 struct Foo(f32)
나 enum Foo { Bar(f32) }
가 f32
와 항상 동일한 ABI를 가지게 하도록 하기 위해서 꼭 필요합니다.
이 표현 방식은 타입의 유일한 필드가 pub
이거나, 그 레이아웃이 단순하게 묘사되었을 경우에만 공개 ABI의 한 부분으로 인정됩니다. 그렇지 않다면 이 레이아웃은 다른 크레이트들이 의존해서는 안됩니다.
더 자세한 내용은 RFC 1758과 RFC 2645에 있습니다.
repr(u*), repr(i*)
이것들은 필드 없는 열거형을 만들기 위한 크기와 부호를 지정합니다. 식별자가 이 정수를 벗어나서 오버플로우되면 컴파일할 때 에러가 발생할 겁니다. 하지만 이것을 러스트에서 허용할 수도 있습니다: 오버플로우된 순간 0이 되게 명시적으로 말하는 것이죠. 그러나 러스트는 열거형의 두 개의 형이 같은 식별자를 가지는 것을 허용하지는 않을 겁니다.
"필드 없는 열거형"이라는 용어는 단지 열거형이 그 형에서 데이터를 가지지 않는다는 것을 말합니다. repr(u*)
나 repr(C)
가 없는 필드 없는 열거형은 여전히 러스트 타입이고, 안정적인 ABI 표현이 존재하지 않습니다.
repr
을 더하는 것은 ABI를 위해 이 열거형이 지정된 타입의 정수로 다뤄지게 합니다.
만약 열거형이 필드가 있다면, 타입의 정해진 레이아웃이 있다는 점에서 효과는 repr(C)
의 효과와 비슷하게 됩니다. 이것은 열거형을 C 코드에 넘기고, 그 타입의 실제 표현을 접근하고 직접 그 태그와 필드를 조작할 수 있게 해 줍니다.
자세한 내용은 RFC를 참고하세요.
이 repr
은 구조체에는 아무 효과도 없습니다.
필드가 있는 열거형에 명시적인 repr(u*)
, repr(i*)
, 혹은 repr(C)
를 추가하는 것은 널 포인터 최적화를 억제하는데, 이것은 다음과 같습니다:
#![allow(unused)] fn main() { 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 칩들). 이렇게 "압축"된 필드를 직접 읽거나 저장하는 간단한 경우들에서는, 컴파일러가 쉬프트나 마스크 같은 것들로 정렬 문제를 간단히 수정할 수 있을지도 모릅니다. 그러나 그 압축된 필드에 레퍼런스를 단다면, 컴파일러가 정렬되지 않은 읽기를 피할 수 있는 코드를 낼 수 있을 것 같지는 않습니다.
이것이 미정의 동작을 일으킬 수 있기 때문에, 린트가 구현되었고 지금은 아주 심각한 오류가 될 것입니다.
repr(packed)
는 가볍게 사용되어서는 안됩니다. 극심하게 이것이 필요하지 않다면 이것은 쓰여져서는 안됩니다.
이 표현은 repr(C)
나 repr(Rust)
와 함께 쓸 수 있습니다.
repr(align(n))
repr(align(n))
은 (n
은 2의 거듭제곱입니다) 타입이 최소 n의 정렬선을 가지도록 요구합니다.
이것은 여러 가지 장난을 가능하게 해 주는데, 예를 들어 배열의 이웃하는 원소들이 서로의 캐시 선을 절대 공유하지 않도록 하는 것이 있습니다 (이것으로 어떤 종류의 동시성 코드는 빠르게 할 수 있습니다).
이 표현은 repr(C)
나 repr(Rust)
와 함께 쓸 수 있지만, repr(packed)
와는 함께 쓸 수 없습니다.