Rust의 `only` 경계 제안과 더 풍부한 `Sized` 계층, 그리고 미래의 기본 경계 계열로의 확장 가능성을 살펴봅니다.
only 경계는 여러분이 한 번도 들어보지 못했을 Rust의 가장 큰 변화가 될 가능성이 있습니다. 이 기능은 현재 Arm 팀(David Wood, Rémy Rakic 외)에 의해 Sized Hierarchy and Scalable Vector Extension 프로젝트 목표의 일부로 설계 및 개발되고 있습니다. 이 글에서는 이 기능을 살펴보고, 설계에 관한 특정 질문 하나에 답해 보려 합니다(경계의 범위에 대한 이야기인데, 곧 설명하겠습니다). 하지만 본격적으로 들어가기 전에, 먼저 약간의 배경 설명을 하고 싶습니다.
Sized 경계가 있습니다오늘날의 Rust에서는 모든 타입 매개변수(Self 제외)에 Sized라는 기본 경계가 붙습니다:
// So this function...
fn identity<T>(t: T) -> T {
t
}
// ...is actually short for
fn identity<T>(t: T) -> T
where
T: Sized, // <-- Added by default!
{
t
}
타입 T는 컴파일러가 컴파일 시점에 T 값의 크기를 계산할 수 있으면 Sized를 구현합니다. 이것은 몇 가지 눈에 띄는 예외를 빼면 거의 모든 타입에 대해 참입니다. 예를 들어 [u32]는 “어떤 개수의 u32 인스턴스”를 가리킵니다. u32 하나가 4바이트라는 것은 알지만, u32가 몇 개인지 모르면 [u32]의 크기를 알 수 없습니다. 이는 스택 위에 [u32] 타입의 값을 둘 수 없다는 뜻입니다(스택 프레임 크기를 얼마로 잡아야 할까요?).
?Sized로 옵트아웃합니다하지만 by_ref처럼 값을 참조로만 받는(즉, 포인터로 받는) 함수가 있다면, [u32] 값의 크기를 알 필요는 없어야 합니다. 왜냐하면 그 값을 직접 조작하지 않기 때문입니다. 이런 경우 Sized를 요구하지 않는 타입 매개변수 U를 둘 수 있지만, 기본 경계에서 명시적으로 “옵트아웃”해야 합니다:
fn by_ref<U>(t: &U)
where
U: ?Sized, // <-- Opt out from the default
{ }
재미있는 역사 이야기로, 이 시스템은 Dynamically Sized Types를 수용하기 위해 2014년에 도입되었습니다. 그 이전에는 &[u32]가 실제로 내장된, 더 이상 나눌 수 없는 타입이었습니다. 한동안은 [u32]/&처럼 쓰기도 했습니다.1
Sized 대 ?Sized만으로는 필요한 모든 것을 표현하기에 충분하지 않습니다Sized 대 ?Sized 설계는 꽤 잘 버텨 왔지만, 한계도 드러내고 있습니다. “값이 정적으로 계산 가능한 크기를 가진다” 대 “각 값이 런타임에 계산 가능한 서로 다른 크기를 가진다”라는 구분만으로는 우리가 원하는 모든 것을 포괄할 수 없다는 것이 밝혀졌습니다. 예를 들어 extern 타입은 런타임에서도 값의 크기를 알 수 없는 타입입니다. 그리고 Arm의 Scalable Vector Extension은 타입의 모든 값이 같은 크기를 갖되(str이나 [T]처럼 값마다 길이가 다른 것이 아니라), 그 크기가 런타임이 되어서야 결정되는 SIMD 타입을 표현하고자 합니다.
Sized 계층단순히 Sized 또는 ?Sized만 둘 것이 아니라, 우리가 वास्तव로 원하는 것은 더 풍부한 계층입니다. 현재 계획은 대략 다음과 같습니다:
where
trait Sized는 모든 값이 같은 크기를 가지며, 그 크기를 타입만 알고도 계산할 수 있음을 의미합니다.trait MetadataSized는 값마다 크기가 다를 수 있으며, 그 크기를 값에 대한 참조에 붙어 있는 메타데이터가 주어지면 계산할 수 있음을 의미합니다. 예로는 [T]나 dyn Trait가 있습니다.trait MaybeSized는 모든 값에 대해 구현되며, 값의 크기에 대해서는 아무것도 말해 주지 않습니다.두 가지 주의점이 있습니다:
?Sized 표기법은 이 계층으로 확장되지 않습니다하지만 이제 문제가 생깁니다. ?Sized 표기법은 사용자가 자신이 옵트아웃하려는 기본 경계를 지정한다는 생각을 전제로3 합니다. 즉, ?는 “이것이 Sized인지 아닌지 모르겠다”를 뜻하도록 의도되었습니다(기본값에서는 Sized임을 안다는 것과 대비됩니다). 그런데 경계에서 “옵트아웃”하는 방식은 다단계 계층과는 잘 맞지 않습니다. ?Sized를 쓰면, 이것은 T: MetadataSized(하지만 T: Sized는 아님)에 대응하는 걸까요? 나중에 T: MetadataSized와 T: Sized 사이에 또 다른 단계를 넣고 싶어지면 어떨까요? 그러면 T: ?Sized의 의미를 바꿔서 새로운 경계를 가리키게 하거나, 혹은 T: ?Sized가 계층을 두 단계 내려가도록 해야 합니다. 더 성가신 점은, 그 중간 단이 아직 불안정한 동안에는 어떻게 하느냐는 것입니다. 설마 T: ?Sized가 불안정한 트레이트를 가리키게 할 수는 없겠죠… 나중에 그걸 제거하기로 결정하면 어떡합니까
only 경계새 제안은 T: ?Sized 대신 T: only MetadataSized 또는 T: only UnknownSized라고 쓰는 것입니다. only 경계는 두 가지를 결합합니다:
T: only MetadataSized는 T가 최소한 MetadataSized는 구현해야 함을 뜻합니다.T: Sized 경계를 추가하지 않습니다.only라는 이름은 T: Sized가 T: MetadataSized를 함의한다는 사실에서 왔습니다. 그래서 기본값인 T: Sized는 이미 공짜로 T: MetadataSized도 의미합니다. 하지만 only MetadataSized라고 쓰면 “전체 계층은 필요 없고, MetadataSized면 충분하다”라고 말하는 셈입니다.
only 경계는 일반 경계처럼 동작합니다: 필요한 것을 요구하세요only 경계의 좋은 점 하나는, 이것이 일반 경계와 더 비슷하게 동작한다는 것입니다. ? 경계가 “이건 필요 없다”라고 말하는 반면, only 경계는 여러분이 무엇이 필요한지 말합니다. 예를 들어 타입 T의 값들에 대한 참조만 다루고, 그 크기가 무엇인지는 신경 쓰지 않는 함수를 쓴다면 다음처럼 작성할 수 있습니다.
fn by_ref<U>(u: &U)
where
U: only MaybeSized,
{}
타입 V의 값 크기를 실제로 계산해야 하는 함수를 쓴다면, 그 능력을 요구할 수 있습니다:
fn checks_size<V>(v: &V)
where
V: only MetadataSized,
{
std::mem::size_of_val(v)
}
only 경계는 나중에 새로운 단계를 추가할 수 있게 해 줍니다only 경계의 또 다른 좋은 점은, 나중에 계층에 새 단계를 추가하더라도 자연스럽게 동작한다는 것입니다. 예를 들어 크기는 컴파일 시점에 알 수 없지만 정렬(alignment)은 알 수 있는 Aligned 같은 것을 추가하고 싶다고 해 봅시다. 그러면 계층을 다음처럼 바꿀 수 있습니다:
trait Sized: Aligned
trait Aligned: MetadataSized // <-- new!
trait MetadataSized: MaybeSized
trait MaybeSized
그러면 U: only MaybeSized를 가진 함수(by_ref 같은)와 V: only MetadataSized를 가진 함수(checks_size 같은)는 계속 같은 요구사항을 유지합니다. 반면 새로운 함수는 T: only Aligned를 사용해 작성할 수 있고, 새 경계를 활용할 수 있습니다. 그리고 안정화와도 충돌하지 않습니다. T: only Aligned를 쓰는 코드는 그 중간 계층이 확정될 때까지 불안정한 것으로 간주하면 됩니다.
only 경계는 자연스럽게 합성됩니다다른 모든 경계와 마찬가지로 only 경계도 다른 경계와 결합되어 전체 요구사항을 이룹니다. 따라서 예를 들어 T: only MetadataSized + Sized라고 쓰는 것도 가능합니다. 이것은 T: Sized와 동등하고, 따라서 기본값과도 동등하므로 결국 별 의미는 없지만, 쓸 수는 있습니다. 마찬가지로 trait Clone: Sized라는 점을 감안하면, T: only MetadataSized + Clone라고 쓰는 것도 별 의미가 없습니다. 그냥 동등한 T: Clone이라고 쓰면 되기 때문입니다. 이런 경우에는 기본적으로 경고하는 lint를 둘 계획입니다.
only를 다른 “기본 경계 계열”로 확장하기 (추측적 논의)only 경계의 마지막 강점은, 이것이 완전히 새로운 _기본 경계 계열_을 도입할 수 있게 해 준다는 점입니다. 한 가지 예는 Move 경계를 도입하자는 아이디어입니다. 이것은 별개의 기능이며, 현재 RFC에 포함되어 있지 않다는 점에 유의하세요.
오늘날 Rust의 모든 타입은 “이동 가능”하고 “잊을 수 있음” 상태입니다. 즉, 이전 위치를 더 이상 사용하지 않는 한 값을 메모리 복사(memcpy)로 다른 곳으로 옮길 수 있고, 또한 값의 소멸자를 실행하지 않고도 그 값이 저장된 메모리를 재활용할 수 있습니다. 눈에 띄는 예외가 하나 있는데, 바로 값을 pin 하면 더 이상 이동할 수 없고, 그 메모리를 재사용하기 전에 반드시 소멸자를 실행해야 합니다. 하지만 그 외에는 이것이 아주 엄격한 규칙입니다. 그리고 이것은 성가십니다!
문제는 소멸자가 실행된다는 보장을 할 수 없다는 점이 많은 unsafe 코드 패턴을 막는다는 것입니다. 예를 들어 rayon 스타일의 scoped task는 안전성을 위해 소멸자에 의존합니다. 동기 코드에서는 이것이 동작합니다. 값의 소멸자를 실행하지 않고 스택 프레임을 벗어나는 것은 UB라고 정했기 때문에, 지역 변수를 스택에 두면 그 소멸자가 실행될 것이라고 확신할 수 있기 때문입니다. 하지만 async 코드에서는 이 방식이 통하지 않습니다! 그리고 소멸자를 실행하지 않고 언와인드하는 것이 유용한 경우도 있습니다.
해법은 기본 트레이트의 두 번째 계열을 도입하는 것입니다. 앞서 본 Sized 계열과 달리, 이 계열은 해당 타입의 값을 어떻게 사용할 수 있는지에 대한 더 세분화된 능력을 정의합니다:
이 트레이트들의 의미는 다음과 같습니다:
Forget은, 값의 소멸자를 실행하지 않고도 그 값의 메모리를 재활용할 수 있음을 뜻합니다.Leak은 값의 소멸자를 실행하지 않아도 되지만, 그 값이 놓여 있던 메모리를 절대 재사용하지 않는 경우에만 가능함을 뜻합니다.Destruct는 이 타입의 값을 가지고 있다면, 그 소멸자를 실행함으로써 그 값이 놓여 있던 메모리를 재사용할 수 있음을 뜻합니다.Copy는, 그 위치를 메모리 복사한 뒤에도 원래 위치를 계속 사용할 수 있음을 뜻합니다. 엄밀히는 기본값은 아니지만 관련이 있어서 포함했습니다.Move는, 원래 위치의 사용을 중단하는 한 값을 메모리 복사해 새 위치로 옮길 수 있음을 뜻합니다.Access는 이 계열의 루트입니다. 이것은 값을 “제자리에서 접근할 수 있다”는 뜻입니다(기본적으로 어떤 값이든 해당합니다).이로 인해 컴파일러에 새로운 검사가 들어갑니다:
a = b이고 이후 b를 사용하지 않을 때), 그 타입이 Move를 구현하는지 검사합니다(오늘날에는 항상 허용됩니다).Destruct를 구현하는지 검사합니다.몇 가지 함의가 있습니다:
T: only Destruct 타입의 값을 소유한다면, 함수가 반환되기 전에 그 값을 반드시 소멸시켜야 합니다. Move를 구현하는지 모르므로 이동할 수 없고, leak 하거나 forget 하는 것도 안 됩니다.T: only Move 타입의 값을 소유한다면, 그 값으로 할 수 있는 유일한 일은 다른 곳으로 이동시키는 것뿐입니다. Destruct를 구현하는지 모르므로 drop 할 수 없습니다.T: only Access 타입의 값을 소유할 수 없습니다. 이동도 drop도 할 수 없어서 반환할 수 없기 때문입니다. 하지만 그런 값이 static에 들어가는 식은 가능할 수 있습니다.only 경계가 어떻게 동작할 수 있는가이 글을 쓰게 된 계기는, 위에서 설명한 것처럼 여러 “기본 트레이트 계열”이 존재할 때 only 경계가 어떻게 동작해야 하는지를 lang 팀 회의에서 질문받았기 때문입니다. 현재 RFC는 Sized 트레이트만 다루고 있지만, 앞으로의 RFC에서는 “access 계열”도 검토할 것으로 예상하므로, 둘 다 포괄하도록 확장되지 않는 결정을 지금 내려서는 안 됩니다.
제가 상상하는 동작 방식은 이렇습니다. 각 기본 트레이트는 하나 이상의 “계열”에 연결됩니다. only 경계가 있으면, 그 트레이트가 속한 각 계열에서 모든 기본 트레이트를 “옵트아웃”합니다:
T: only Move는 Forget, Leak, Destruct에서는 옵트아웃하지만 Sized에서는 그렇지 않습니다.T: only Destruct는 Forget, Leak, Move에서는 옵트아웃하지만 Sized에서는 그렇지 않습니다.T: only MetadataSized는 Sized에서는 옵트아웃하지만 Forget이나 Move에서는 그렇지 않습니다.T: only MaybeSized는 Sized에서는 옵트아웃하지만 Forget이나 Move에서는 그렇지 않습니다.일부 기본값을 다시 “옵트인”하고 싶을 수도 있습니다. 예를 들어 T: only Move + Destruct는 충분히 타당한 표현입니다. 이것은 이동할 수도 있고 소멸시킬 수도 있지만 leak 하거나 forget 할 수는 없는 값을 뜻합니다.
Option::map은 only Move를 요구합니다map은 Move만 있으면 되는 함수의 예입니다. self를 분해해야 하는데(이때 옵셔널 값을 지역 변수 v로 _이동_합니다), 그다음 클로저 op를 호출할 때도 감싸진 값 v를 다시 이동합니다:
impl<T: only Move> Option<T> {
fn map<U: only Move>(
self,
op: impl FnOnce(T) -> U,
) -> Option<U> {
match self {
Some(v) => Some(op(v)),
None => None,
}
}
}
흥미로운 점 하나는 결과 타입 U입니다. 이 글에서 설명한 것만 기준으로 하면, 결과는 Some 값으로 이동되는 등의 이유 때문에 only Move여야 합니다. 하지만 in-place-init이 있으면, Option이 제자리에서 생성되고 그 뒤로 절대 이동되지 않는다고 정적으로 보장할 수 있으므로 이 정의에서 U: only Move 경계를 생략할 수 있습니다.
Option::or는 only Move + Destruct를 요구합니다Option의 a.or(b) 메서드는 a가 Some이면 a를 반환하고, 그렇지 않으면 b를 반환합니다. 이것이 흥미로운 이유는 값 b가 사용되지 않을 수도 있어서 only Move + Destruct 경계가 필요하기 때문입니다.
impl<T: only Move> Option<T> {
fn or(
self,
alternate: Option<T>,
) -> Option<T>
where
T: Destruct, // <-- because it may be dropped
{
match self {
Some(v) => Some(v), // drops `alternate`
None => alternate, // moves `alternate`
}
}
}
Rc는 MaybeSized + Leak를 요구합니다Rc 타입은 두 계열 모두에서 경계를 완화하고 싶은 예입니다:
struct Rc<T: only MaybeSized + only Leak> {}
제가 보기에 Rc의 올바른 최소 경계는 다음과 같습니다:
only MaybeSized: MetadataSized나 Sized인 것들을 저장할 수도 있지만, 반드시 그래야 할 필요는 없고 계산 가능한 크기가 없는 것들도 저장할 수 있기 때문입니다(물론 그럼 어떻게 해제하느냐는 문제가 생기지만, 그것은 allocator의 관심사입니다).only Leak: Rc 값은 사이클을 형성할 수 있으므로 소멸자가 실행된다고 결코 보장할 수 없기 때문입니다. 흥미롭게도 Rc<T>는 내부 값이 그렇지 않더라도 Forget을 구현할 수 있습니다.여기서 글이 조금 헷갈릴 수 있습니다. 현재 RFC는 제안된 “Sized” 트레이트만을 다루고 있습니다. Access 계열은 우리가 탐색 중인 추측적 미래 확장이며, 훨씬 더 초기 단계에 있습니다.
only를 아무 트레이트에나 쓸 수 있나요?초기 계획으로는 only를 잘 알려진 기본 트레이트(Move, Sized 등)에만 사용할 수 있게 할 예정입니다. 다만 장기적으로는 이를 일반화하는 방향에 대한 생각도 일부 있습니다.
제안되었던 다른 대안은 타입 매개변수별로 옵트아웃하는 방식입니다. 그래서 다음과 같이 쓸 수도 있었을 것입니다:
fn foo<T: MetadataSized + ?default>
이렇게 하면 모든 기본 경계에서 옵트아웃하게 됩니다. 문법은 당연히 더 논의해야 하겠지만, 지금은 무시합시다. 핵심 질문은 모든 기본값에서 옵트아웃하는 것이, 하나의 계열에서만 옵트아웃하는 것보다 더 나으냐는 것입니다. 저는 두 가지 이유로 계열별 방식을 선호합니다:
첫째, T: only Move 같은 예는 단 하나의 계열에서만 옵트아웃하면서 기본 Sized 경계는 유지하고 싶을 수 있음을 잘 보여 줍니다. Sized또는Forget에서는 옵트아웃하고 둘 다는 아닌 함수가 많을 가능성이 높다고 생각합니다.
Move: Sized로 만들면 같은 효과를 얻을 수 있다고 생각할 수도 있지만, 저는 그것이 실수라고 봅니다. 값의 크기를 동적으로 계산해야 한다는 사실이, 그 값이 이동될 수 없다는 뜻은 본질적으로 아니기 때문입니다.둘째, 나중에 값의 다른 직교적 성질을 완화하고 싶어져서 새로운 계열을 도입하려 할 때 더 어려워집니다.
SizedxorForget에서는 옵트아웃하고 둘 다는 하지 않으려 할 가능성이 높다고 생각하나요?Forget, Move 같은 트레이트는 주로 소유된 값에 적용되기 때문입니다. 우리가 본 Option<T> 예시들이 꽤 전형적입니다. 그리고 타입 T의 값을 이리저리 이동하고 있다면, 그 T는 Sized여야 합니다.
Rc는 only Leak + only MetadataSized로 두 계열 모두에서 옵트아웃하고 싶어 했잖아요?맞습니다. 그리고 그 특정 조합은 흔할 것이라고 생각합니다. 하지만 그렇다고 해서 ?default 접근법 자체가 더 낫다는 뜻은 아니라고 봅니다. 특히 그 경우에도 코드가 크게 더 깔끔하거나 짧아지지는 않으니까요…
impl<T: ?default + Leak + MetadataSized> Rc<T> {}
…제 생각에 이것이 정말로 시사하는 것은 오히려 _트레이트 별칭과 축약 표기_입니다.
물론입니다! 미래의 RFC에서는 only 경계를 확장해서 “only 경계”를 상위 트레이트로 갖는 트레이트 별칭을 정의할 수 있게 할 수 있다고 생각합니다:
trait RefCountable = only Leak + only MetadataSized;
// Equivalent to:
// trait RefCountable: only Leak + only MetadataSized {}
// impl<T> RefCountable for T where T: only Leak + only MetadataSized {}
그러면 Rc<T>를 정의할 때 only RefCountable 경계를 사용할 수 있습니다:
impl<T: only Refcountable> Rc<T>
only가 없으면, T: Refcountable은 그냥 일반 트레이트 경계일 뿐이고 어떤 기본값에서도 옵트아웃하지 않습니다.
네, 가능합니다! 예를 들어 Value 같은 별칭을 정의할 수 있습니다:
trait Value = only Access + only MaybeSized;
Access와 MaybeSized는 모두 모든 타입에 대해 구현되므로, 이것은 사실상 두 계열 모두의 일부가 됩니다:
그러면 T: only Value라고 해서 두 계열 모두에서 한 번에 옵트아웃할 수 있습니다.
아, 바로 그게 문제입니다. 미래에 새 계열을 추가하고 싶다고 합시다. 예컨대 같은 메모리 공간에 살지 않는 값을 위한 계열(T: only Distributed…?) 같은 것이요. 그러면 Value는 “오래된” 것이 됩니다. Value를 기준으로 작성된 코드는 여전히 단일 메모리 공간 값을 가정하고 있을 테니까요. 하지만 논의된 바와 같이 Value를 에디션 의존 별칭 같은 것으로 만들 수는 있을 것입니다.
Value를 나중에 결정해도 되나요?네! 루트 트레이트는 언제든 도입할 수 있습니다. 그래서 먼저 Sized성 계열을 추가하고, 그다음 Access 계열을 추가한 뒤, 실제 사용 양상을 보고 판단할 수 있습니다. 사람들이 정말로 두 계열 모두에서 자주 옵트아웃한다면, 어떤 별칭들이 유용할 것이고, 어쩌면 Value 변형이 유용할지도 모릅니다.
실제로 후회하게 될 수 있는 유일한 경우는, 사람들이 대개 그냥 둘 다에서 옵트아웃한 다음 원하는 것만 다시 옵트인하는 방식으로 사용한다는 것이 밝혀질 때입니다. 하지만 T: only Move가 흔할 것이라는 점은 이미 알고 있고, 그런 경우 T: only Value + Move + Sized는 분명 더 어색합니다. 그래서 저는 그런 상황이 아주 가능성 높다고 보지는 않습니다.
Drop이 아니라 Destruct인가요?그 이름은 const trait RFC에서 왔습니다. Drop이라는 이름에서 벗어나려는 이유는 몇 가지가 있습니다. 첫째, Drop을 구현하지 않더라도 소멸자를 가질 수 있습니다. Drop은 사실 소멸자 안의 _사용자 제공 로직_을 가리키지만, 컴파일러는 값의 모든 필드를 drop 하기 위한 자체 로직(흔히 “drop glue”라고 부릅니다)도 추가합니다. 둘째 이유는 Drop 트레이트 자체에도 약간의 개정이 필요하다는 점입니다. 그래서 그 이름에서 벗어나면 사용자 정의 로직을 지정하는 다른 방법들(예: pinned self, by-value 등등)을 둘 수 있습니다.
const 트레이트와는 어떻게 상호작용하나요?아주 아름답게 상호작용합니다! 실제로 Arm이 SVE를 위해 제안한 내용은 T: const Sized라는 개념을 도입해 “컴파일 시점에 크기를 계산할 수 있는 타입”을 의미하게 하자는 것입니다. 저는 이것이 꽤 우아하다고 생각합니다. 마찬가지로 T: const Destruct는 const RFC에서 값이 상수 소멸자를 가진다고 말하는 방법으로 제안되었습니다.
T: only Move + Destruct라고 쓰는 건 번거롭습니다. 그냥 Destruct가 Move를 함의하게 해서 T: only Destruct만 쓰게 하면 안 될까요?제가 선형 타입을 도입하려고 처음 제안했을 때는 Destruct가 Move를 상속하도록 했습니다. 그렇게 되면 Option::or 제안은 단순히 U: only Destruct라고만 쓰면 되고 U: only Move + Destruct는 필요 없었을 것입니다. 하지만 Alice Ryhl과 다른 분들이 지적했듯이, 이동 불가능하지만 그래도 반드시 소멸되어야 하는 타입들이 있으므로 둘을 결합하는 것은 타당하지 않습니다.
Project Goal에 자세한 내용이 많이 있습니다. 최신 업데이트는 tracking issue에서 확인할 수 있습니다. 영상을 보는 것을 좋아한다면 David Wood의 Rust Nation talk를 추천합니다.
마무리하면서 메타적인 관찰 하나와 Arm 팀에 대한 큰 찬사를 덧붙이고 싶습니다. 저는 이 팀이 오픈소스가 얼마나 훌륭할 수 있는지를 보여 주고 있다고 생각합니다. Arm 팀의 1차 동기는 Scalable Vector Extension 지원을 추가하는 것입니다. 이것은 Rust가 Arm 프로세서를 최대한 활용할 수 있게 해 줍니다. 이것 자체만으로도 칭찬할 만한 목표이고 Rust에 가치가 있습니다. 제 관점에서 Rust의 강점 중 하나는 여러분의 프로세서가 제공하는 모든 힘에 접근할 수 있게 해 준다는 점이며, 거기에는 고유한 확장 기능도 포함되어야 합니다.
하지만 Arm 팀은 이 기능을 Rust에 대한 일종의 특수 사례 확장으로 추가하는 데서 그치지 않고, 한 걸음 더 나아가 범용적인 개선을 추진하고 있습니다. 이것은 다른 여러 기능들(extern 타입과, 어느 정도는 보장된 소멸자들, 그리고 보장된 소멸자 자체는 scoped async thread와 더 나은 Wasm 통합을 가능하게 합니다)을 열어 줄 것입니다. 저는 그것이 정말 마음에 듭니다.
사실 제 블로그 글 중 하나에서 저는 &str를 표기하는 방법으로 ""를 제안했던 기억이 납니다. 순전히 그 괴상함 때문에라도 실제로 그렇게 했으면 조금 좋았을 것 같기도 합니다(fn foo(name: "")).↩︎
저는 값에 대해 수행할 수 있는 _연산_을 가리키는 이름을 더 선호합니다. 그래서 예를 들어 MetadataSized 대신 SizeOfVal이 더 좋다고 생각합니다. 그 의미가 std::mem::size_of_val 함수를 그 값에 대해 호출할 수 있다는 것이기 때문입니다.↩︎
여러분을 위한 작은 논리학 말장난이었습니다.↩︎