CGP v0.7.0 릴리스는 새로운 주석과 매크로를 통해 Rust에서 컨텍스트 제네릭 함수를 일반 함수 문법으로 작성하고, 암시적 인자·프로바이더 합성·추상 타입 가져오기를 더 적은 보일러플레이트로 구현할 수 있게 한다.
CGP v0.7.0이 출시되어 CGP 매크로 툴킷이 대폭 확장되었습니다. 이번 릴리스의 핵심은 #[cgp_fn], #[implicit], #[uses], #[extend], #[use_provider], #[use_type]로 이루어진 새로운 주석 모음으로, 이전보다 훨씬 적은 보일러플레이트로 일반 함수 문법만으로 컨텍스트 제네릭 코드를 작성할 수 있게 해줍니다.
GitHub, Reddit, Lobsters, Hacker News에서 토론하세요.
처음 오셨다면, Context-Generic Programming(CGP)은 Rust에서 컨텍스트(Self) 타입에 대해 제네릭하게 동작하는 코드를 작성할 수 있게 해주는 모듈형 프로그래밍 패러다임입니다. CGP를 사용하면 Rust의 트레이트 시스템만으로, 그리고 런타임 오버헤드 없이, 수동 보일러플레이트 없이도 다양한 컨텍스트 타입 전반에서 동작하는 함수와 구현을 정의할 수 있습니다.
note
이 릴리스의 세부 내용으로 들어가기 전에, CGP의 동기와 v0.7.0 기능을 이 글보다 훨씬 깊게 다루는 새 면적 계산 튜토리얼을 읽는 것을 강력히 권장합니다.
v0.7.0이 왜 중요한지 이해하려면, 이를 촉발한 기존 Rust의 두 가지 한계를 이해하는 것이 도움이 됩니다.
첫 번째는 명시적 파라미터 스레딩입니다. 일반 Rust 함수가 다른 함수에 값을 전달해야 할 때, 호출 체인의 모든 중간 호출자는 그 값을 인자로 받아서 명시적으로 전달해야 합니다 — 설령 그 중간 호출자가 그 값을 직접 사용하지 않더라도 말입니다. 호출 체인이 길어질수록, 함수 시그니처에는 순전히 하위 호출자의 요구를 만족시키기 위한 파라미터가 쌓이게 됩니다.
두 번째는 구체적인 컨텍스트 struct에 대한 강한 결합입니다. Rust 개발자들은 종종 파라미터 스레딩을 해결하기 위해 값들을 하나의 struct에 묶고 그 위에 메서드를 정의합니다. 이렇게 하면 호출 시그니처는 깔끔해지지만, 구현이 특정 타입 하나에 강하게 결합됩니다. struct가 커지거나 확장되어야 하면 이를 참조하는 모든 것이 영향을 받고, 여러 독립 컨텍스트가 같은 메서드를 공유하게 하려면 코드를 복제하지 않고는 깔끔하게 해결하기 어렵습니다.
v0.7.0에서 도입된 CGP의 #[cgp_fn] 매크로와 #[implicit] 인자는 이 두 문제를 동시에 해결합니다.
#[cgp_fn] macrov0.7.0의 중심에는 #[cgp_fn] 매크로가 있으며, 이를 통해 일반 함수 문법으로 컨텍스트 제네릭 코드를 작성할 수 있습니다. #[cgp_fn]으로 장식된 함수는 _제네릭 컨텍스트_를 가리키는 &self 파라미터를 받으며, 어떤 인자든 #[implicit]로 표시하여 호출자가 전달하는 대신 컨텍스트에서 자동으로 추출하도록 지정할 수 있습니다.
예를 들어, 직사각형의 면적을 계산하는 컨텍스트 제네릭 함수는 다음과 같이 정의합니다:
#[cgp_fn]pub fn rectangle_area( &self, #[implicit] width: f64, #[implicit] height: f64,) -> f64 { width * height}
여기서는 세 가지 주석이 핵심 역할을 합니다. #[cgp_fn]은 일반 함수를 보강하여 컨텍스트 제네릭 capability로 바꿉니다. &self는 이 함수가 호출되는 어떤 컨텍스트에 대한 참조를 제공합니다. 그리고 width와 height의 #[implicit]는 호출자가 값을 제공하도록 요구하는 대신 &self에서 자동으로 가져오라고 CGP에 지시합니다.
함수 본문 자체는 완전히 일반적인 Rust이며 — 주석 외에 새로 배울 개념이 없습니다.
이 함수를 구체 타입에서 사용하려면 최소한의 컨텍스트를 정의하고, 제네릭 필드 접근을 가능하게 하기 위해 #[derive(HasField)]를 적용합니다:
#[derive(HasField)]pub struct PlainRectangle { pub width: f64, pub height: f64,}
#[derive(HasField)] 매크로는 CGP가 필드 이름으로 PlainRectangle의 필드에 제네릭하게 접근할 수 있도록 하는 구현을 생성합니다. 이를 바탕으로 rectangle_area를 메서드처럼 호출할 수 있습니다:
let rectangle = PlainRectangle { width: 2.0, height: 3.0,};let area = rectangle.rectangle_area();assert_eq!(area, 6.0);
이게 전부입니다. CGP가 필드를 함수 인자로 자동 전파합니다. HasField를 derive하는 것 외에 PlainRectangle에 대해 어떤 구현도 작성할 필요가 없습니다.
#[uses]컨텍스트 제네릭 함수의 가장 가치 있는 성질 중 하나는 서로 조합될 수 있다는 점입니다. #[uses] 속성은 CGP 함수가 다른 CGP 함수를 의존성으로 가져오게 해주며, 호출자는 가져온 함수 자체의 요구 사항을 전혀 알 필요 없이 self에서 이를 호출할 수 있습니다.
예를 들어, 내부에서 rectangle_area를 호출하는 scaled_rectangle_area는 다음과 같이 정의합니다:
#[cgp_fn]#[uses(RectangleArea)]pub fn scaled_rectangle_area( &self, #[implicit] scale_factor: f64,) -> f64 { self.rectangle_area() * scale_factor * scale_factor}
#[uses(RectangleArea)]는 RectangleArea 트레이트를 가져옵니다 — 이는 #[cgp_fn]이 함수 이름 rectangle_area에서 유도하는 CamelCase 이름입니다. width와 height는 이미 rectangle_area 내부에서 소비되므로, 우리는 scale_factor만 암시적 인자로 선언하면 됩니다.
scaled_rectangle_area를 정의한 뒤, scale_factor 필드를 추가한 두 번째 컨텍스트를 도입할 수 있습니다:
#[derive(HasField)]pub struct ScaledRectangle { pub scale_factor: f64, pub width: f64, pub height: f64,}
PlainRectangle과 마찬가지로 #[derive(HasField)]만 필요합니다. 이제 두 컨텍스트는 독립적으로 공존할 수 있습니다:
let rectangle = ScaledRectangle { scale_factor: 2.0, width: 3.0, height: 4.0,};let area = rectangle.rectangle_area();assert_eq!(area, 12.0);let scaled_area = rectangle.scaled_rectangle_area();assert_eq!(scaled_area, 48.0);
중요한 점은 PlainRectangle을 전혀 수정하지 않는다는 것입니다. PlainRectangle은 자체적으로 rectangle_area를 계속 지원하며, scaled_rectangle_area는 scale_factor 필드도 함께 지닌 컨텍스트에서만 사용할 수 있습니다. 서로 모르는 두 독립 컨텍스트가 같은 함수 정의를 공유할 수 있으며, 어느 한쪽도 다른 쪽을 알 필요가 없습니다.
#[extend]#[uses] 속성은 모듈 구성요소를 가져오는 Rust의 use 문과 유사합니다. 즉, 가져온 CGP 함수들은 _impl-side dependency_를 사용해 생성된 where 바운드 뒤에 숨습니다.
#[extend] 속성은 다른 CGP 함수를 가져오고, 더 나아가 재익스포트하여, 당신의 함수를 가져오는 누구에게나 그 함수가 보이도록 해줍니다. 이는 모듈 구성요소를 재익스포트하는 Rust의 pub use와 유사합니다.
예를 들어, scaled_rectangle_area를 #[uses] 대신 #[extend]로 다시 쓸 수 있습니다:
#[cgp_fn]#[extend(RectangleArea)]pub fn scaled_rectangle_area( &self, #[implicit] scale_factor: f64,) -> f64 { self.rectangle_area() * scale_factor * scale_factor}
이는 ScaledRectangleArea를 가져오는 어떤 구성요소든 RectangleArea에도 접근할 수 있음을 의미합니다. 예를 들어:
#[cgp_fn]#[uses(ScaledRectangleArea)]pub fn print_scaled_rectangle_area(&self) { println!( "The area of the rectangle is {}, and its scaled area is {}", self.rectangle_area(), self.scaled_rectangle_area(), );}
print_scaled_rectangle_area 함수는 ScaledRectangleArea만 가져오면 되지만, self에서 rectangle_area와 scaled_rectangle_area를 모두 호출할 수 있습니다.
#[implicit] in #[cgp_impl]CGP v0.7.0에서는 #[cgp_impl] 내부에서 #[implicit] 인자를 사용하는 것도 지원합니다. #[cgp_impl]은 CGP 컴포넌트에 대한 이름 있는 프로바이더 구현을 작성할 때 사용됩니다. 이는 #[cgp_component]로 정의된 트레이트를 구현할 때 특히 유용합니다.
예를 들어, AreaCalculator 컴포넌트와 그에 대한 이름 있는 프로바이더를 암시적 인자로 정의하는 방법은 다음과 같습니다:
#[cgp_component(AreaCalculator)]pub trait CanCalculateArea { fn area(&self) -> f64;}#[cgp_impl(new RectangleAreaCalculator)]impl AreaCalculator { fn area( &self, #[implicit] width: f64, #[implicit] height: f64, ) -> f64 { width * height }}
v0.7.0 이전에는 동일한 결과를 얻기 위해 #[cgp_auto_getter]로 별도의 getter 트레이트를 정의하고, 이를 프로바이더의 where 절에 추가한 뒤, 그 getter 메서드들을 명시적으로 호출해야 했습니다:
#[cgp_auto_getter]pub trait HasRectangleFields { fn width(&self) -> f64; fn height(&self) -> f64;}#[cgp_impl(new RectangleAreaCalculator)]impl AreaCalculatorwhere Self: HasRectangleFields,{ fn area(&self) -> f64 { self.width() * self.height() }}
#[implicit]를 사용하면 이 보일러플레이트 계층 전체가 사라집니다. width와 height 값은 컨텍스트에서 직접 가져오며, getter 트레이트, where 절, 개별 메서드 호출을 수동으로 유지할 필요가 없습니다. 내부적으로 #[cgp_impl]의 #[implicit]는 의미적으로 #[cgp_auto_getter]와 동등하며 비용 또한 동일하게 0입니다.
#[use_provider]CGP v0.7.0은 또한 상위 차수 프로바이더 구현 안에서 다른 프로바이더를 인체공학적으로 가져오기 위한 #[use_provider] 속성을 도입합니다. 이는 계산의 일부를 플러그 가능한 내부 프로바이더에 위임하는 프로바이더를 만들 때 특히 유용합니다.
예를 들어, 어떤 내부 AreaCalculator 프로바이더를 감싸고 결과에 스케일 팩터를 적용하는 일반적인 ScaledAreaCalculator를 원한다고 합시다. 이제 다음과 같이 작성할 수 있습니다:
#[cgp_impl(new ScaledAreaCalculator<InnerCalculator>)]#[use_provider(InnerCalculator: AreaCalculator)]impl<InnerCalculator> AreaCalculator { fn area(&self, #[implicit] scale_factor: f64) -> f64 { InnerCalculator::area(self) * scale_factor * scale_factor }}
#[use_provider] 속성은 InnerCalculator가 AreaCalculator 프로바이더 트레이트를 구현해야 한다고 선언합니다. 이 속성이 없던 이전에는, 명시적인 Self 파라미터와 함께 where 절에 같은 제약을 수동으로 적어야 했습니다:
#[cgp_impl(new ScaledAreaCalculator<InnerCalculator>)]impl<InnerCalculator> AreaCalculatorwhere InnerCalculator: AreaCalculator<Self>,{ fn area(&self, #[implicit] scale_factor: f64) -> f64 { InnerCalculator::area(self) * scale_factor * scale_factor }}
주요한 인체공학적 개선은 #[use_provider]가 프로바이더 트레이트의 첫 번째 제네릭 파라미터로 Self를 자동으로 삽입해준다는 점입니다. 덕분에 내부 차이를 이해할 필요 없이 프로바이더 트레이트를 소비자 트레이트처럼 다룰 수 있습니다. 이후 이 프로바이더는 delegate_components!를 통해 어떤 컨텍스트에도 합성할 수 있습니다:
delegate_components! { ScaledRectangle { AreaCalculatorComponent: ScaledAreaCalculator<RectangleAreaCalculator>, }}
이는 CGP 프로바이더가 그저 일반적인 Rust 타입이며, ScaledAreaCalculator<RectangleAreaCalculator> 같은 상위 차수 프로바이더는 단지 제네릭 타입 인스턴스화에 불과하다는 점을 보여줍니다. 새로운 런타임 개념은 전혀 없습니다.
#[use_type]CGP v0.7.0은 또한 추상 연관 타입을 인체공학적으로 가져오기 위한 #[use_type] 속성을 도입합니다. 이를 통해 f32, f64 또는 다른 수치 타입일 수 있는 Scalar 타입 같은 추상 타입을 다루는 컨텍스트 제네릭 함수를, 모든 곳에 Self:: 접두사를 쓰지 않고도 작성할 수 있습니다.
예를 들어, HasScalarType 트레이트에서 Scalar 연관 타입을 가져와 어떤 스칼라 타입에 대해서도 제네릭한 rectangle_area 버전을 정의하는 방법은 다음과 같습니다:
pub trait HasScalarType { type Scalar: Mul<Output = Scalar> + Copy;}#[cgp_fn]#[use_type(HasScalarType::Scalar)]pub fn rectangle_area( &self, #[implicit] width: Scalar, #[implicit] height: Scalar,) -> Scalar { width * height}
#[use_type]가 없으면 같은 함수는 전체적으로 Self::Scalar를 사용해야 해서 더 시끄러워집니다. 내부적으로 #[use_type(HasScalarType::Scalar)]는 #[extend(HasScalarType)]로 디슈거링되며, bare 식별자 Scalar에 대한 모든 참조를 다시 Self::Scalar로 되돌려 씁니다:
#[cgp_fn]#[extend(HasScalarType)]pub fn rectangle_area( &self, #[implicit] width: Self::Scalar, #[implicit] height: Self::Scalar,) -> Self::Scalar { width * height}
이제 서로 다른 스칼라 타입을 사용하는 컨텍스트 타입을 정의할 수 있습니다. 예를 들어, f64 대신 f32를 사용하는 직사각형은 다음과 같습니다:
#[derive(HasField)]pub struct F32Rectangle { pub width: f32, pub height: f32,}impl HasScalarType for F32Rectangle { type Scalar = f32;}
그리고 rectangle_area()는 f32 값과도 매끄럽게 동작합니다:
let f32_rectangle = F32Rectangle { width: 3.0, height: 4.0,};assert_eq!(f32_rectangle.rectangle_area(), 12.0);
#[use_type] 속성은 #[cgp_component]와 #[cgp_impl] 모두에서 지원되며, CGP 표면 전체에 걸쳐 일관되게 사용할 수 있습니다:
#[cgp_component(AreaCalculator)]#[use_type(HasScalarType::Scalar)]pub trait CanCalculateArea { fn area(&self) -> Scalar;}#[cgp_impl(new RectangleArea)]#[use_type(HasScalarType::Scalar)]impl AreaCalculator { fn area( &self, #[implicit] width: Scalar, #[implicit] height: Scalar, ) -> Scalar { width * height }}
"implicit"이라는 단어는 Scala의 implicit 파라미터 시스템에 익숙한 개발자들에게 경고 신호로 들릴 수 있습니다 — 혼란스러운 오류, 모호한 해석, 추적하기 어려운 코드로 이어질 수 있다는 평판이 잘 알려져 있기 때문입니다. 타당한 우려이며, 직접 답할 가치가 있습니다: CGP의 #[implicit] 속성은 Scala implicits와 표면적으로는 같은 동기(호출 지점의 보일러플레이트 감소)를 공유하지만, 중요한 부분에서의 내부 메커니즘은 범주적으로 다릅니다.
해결 범위. Scala에서는 컴파일러가 지역 변수, companion object, import에 걸친 넓고 계층적인 _implicit scope_를 탐색합니다 — 즉, implicit 값이 거의 어디서든 나타날 수 있습니다. CGP에서 #[implicit]는 항상 self의 필드로만 해석되며, 그 외의 어디에서도 오지 않습니다. 주변 환경도 없고, companion object 탐색도 없고, 고려해야 할 import도 없습니다.
모호성 없음. Scala의 타입만 기반으로 하는 해석에서는 같은 타입의 값이 스코프에 두 개 있으면 모호성이 발생하여 명시적으로 구분해야 합니다. CGP는 _이름과 타입 둘 다_로 해석합니다: #[implicit] width: f64는 f64 타입의 width라는 이름의 필드를 정확히 찾습니다. Rust struct는 같은 이름의 필드를 두 개 가질 수 없으므로, CGP의 암시적 인자는 구조적으로 모호하지 않습니다.
투명한 디슈거링. 모든 #[implicit] 주석은 기계적으로 HasField 트레이트 바운드와 get_field 호출 — 어떤 개발자든 읽고 검증할 수 있는 일반 Rust 구성요소 — 로 확장됩니다. 숨겨진 해석 단계도 없고, 특별한 컴파일러 마법도 없으며, "implicit 지옥"이 누적될 위험도 없습니다.
이번 릴리스에 맞춰, 두 개의 새 면적 계산 튜토리얼이 공개되었으며, 일차 원리부터 CGP의 전체 기능 세트를 쌓아 올리는 방식으로 구성되어 있습니다.
Context-Generic Functions 튜토리얼은 일반 Rust에서 시작해 #[cgp_fn], #[implicit], #[uses]를 소개합니다. rectangle_area가 Rust 트레이트와 blanket impl로 완전히 디슈거링되는 과정을 다루고, HasField 기반의 비용 0 필드 접근 모델을 설명하며, 다른 생태계에서 온 독자를 위해 CGP의 암시적 인자를 Scala의 implicit 파라미터와 비교합니다.
Static Dispatch 튜토리얼은 통합된 CanCalculateArea 인터페이스를 동기부여하기 위해 두 번째 도형인 원을 도입합니다. Rust의 coherence 제한을 구체적인 문제로 보여준 뒤, #[cgp_component]와 #[cgp_impl]로 정의된 이름 있는 프로바이더를 사용해 이를 해결합니다. 마지막으로 구성 가능한 정적 디스패치를 위한 delegate_components!와 상위 차수 프로바이더 합성을 위한 #[use_provider]를 다룹니다.
두 튜토리얼은 순서대로 읽도록 설계되었으며, 기본적인 Rust 친숙함 외에는 CGP에 대한 사전 지식을 요구하지 않습니다.
CGP v0.7.0은 LLM을 위한 agent skills에 대한 예비 지원을 함께 제공합니다. CGP Skills 문서는 LLM에게 CGP를 간결하게 가르치기 위해 특별히 작성되었습니다.
LLM의 도움을 받아 CGP를 사용해보고 싶다면, 프롬프트에 CGP skill을 포함하는 것을 권장합니다. 그러면 어떤 CGP 개념이든 명확히 해달라고 요청할 수 있습니다.
v0.7.0에는 몇 가지 작은 호환성 파괴 변경이 포함되어 있습니다. 기존 CGP 코드의 대다수는 영향을 받지 않으며, 아래 섹션에서는 확인할 사항과 마이그레이션 방법을 설명합니다.
#[cgp_context]#[cgp_context] 매크로는 v0.6.0에서 deprecate된 이후 제거되었습니다. 이제는 컨텍스트 타입을 별도의 CGP 매크로 없이도 직접 정의하는 것이 관용적입니다.
영향을 받는 코드는 v0.6.0 글의 마이그레이션 가이드를 따라, {Context}Components 위임 테이블을 통해서가 아니라 컨텍스트 타입 자체를 위임에 직접 사용하면 됩니다.
#[cgp_component]가 생성하는 소비자 트레이트의 blanket impl이 단순화되었습니다. 예를 들어 다음이 주어졌을 때:
#[cgp_component(Greeter)]pub trait CanGreet { fn greet(&self);}
생성되는 blanket impl은 이제 다음과 같습니다:
impl<Context> CanGreet for Contextwhere Context: Greeter<Context>,{ fn greet(&self) { Context::greet(self) }}
즉, Context 타입은 자신을 컨텍스트 타입으로 하여 프로바이더 트레이트를 구현하기만 하면 소비자 트레이트도 구현한 것으로 간주됩니다.
이전에는 blanket impl에 프로바이더 트레이트와 유사한 추가 테이블 조회가 포함되어 있었습니다:
impl<Context> CanGreet for Contextwhere Context: DelegateComponent<GreeterComponent>, Context::Delegate: Greeter<Context>,{ fn greet(&self) { Context::Delegate::greet(self) }}
프로바이더 트레이트의 blanket impl이 이미 DelegateComponent 조회를 수행하므로, 소비자 트레이트는 더 이상 이를 반복할 필요가 없습니다. 또한 이 변경은, 컨텍스트가 자기 자신을 프로바이더로 동작시키는 특수한 경우에 프로바이더 트레이트 구현만으로도 소비자 트레이트를 바로 만족시킬 수 있다는 좋은 성질을 제공합니다.
note
이 변경의 결과로, 소비자 트레이트와 프로바이더 트레이트가 모두 스코프에 있을 때 컨텍스트에서 정적 메서드를 호출하면 모호성이 발생할 수 있습니다. delegate_components!를 통해 소비자 트레이트를 구현하는 컨텍스트는 동시에 자기 자신의 프로바이더이기도 하므로, Rust는 명시적인 self 리시버 없이는 어떤 트레이트 구현을 사용해야 할지 결정할 수 없습니다. self를 통한 호출은 영향을 받지 않습니다.
check_components! and delegate_and_check_components!#[cgp_context]가 제거되면서, 위임 조회 테이블을 항상 컨텍스트 타입에 직접 구축하는 것이 관용적이 되었습니다. 이에 맞춰 check_components!와 delegate_and_check_components! 매크로도 업데이트되었습니다.
이제 check 트레이트 이름을 생략할 수 있습니다:
delegate_and_check_components! { ScaledRectangle { AreaCalculatorComponent: ScaledAreaCalculator<RectangleAreaCalculator>, }}
check_components! { ScaledRectangle { AreaCalculatorComponent, }}
기본값으로 매크로는 __CanUse{Context}라는 이름의 check 트레이트를 생성합니다. 이 이름은 #[check_trait] 속성으로 오버라이드할 수 있습니다:
delegate_and_check_components! { #[check_trait(CanUseScaledRectangle)] ScaledRectangle { AreaCalculatorComponent: ScaledAreaCalculator<RectangleAreaCalculator>, }}check_components! { #[check_trait(CheckScaledRectangle)] ScaledRectangle { AreaCalculatorComponent, }}
다음의 오래된 문법은 이제 더 이상 유효하지 않습니다:
delegate_and_check_components! { CanUseScaledRectangle for ScaledRectangle; ScaledRectangle { AreaCalculatorComponent: ScaledAreaCalculator<RectangleAreaCalculator>, }}check_components! { CheckScaledRectangle for ScaledRectangle { AreaCalculatorComponent, }}
이 변경의 이유는, 매크로 호출 시작 부분의 선택적 attribute를 파싱하는 것이 for 키워드 앞의 선택적 이름을 파싱하는 것보다 더 간단하기 때문입니다. #[check_trait] 문법은 구현하기도 더 쉽고, 다른 CGP 매크로들이 선택적 설정을 받는 방식과도 더 일관됩니다.
#[check_params] in delegate_and_check_components!delegate_and_check_components! 매크로는 이제 제네릭 파라미터를 가지는 CGP 컴포넌트를 위해 #[check_params]를 지원합니다. 예를 들어 다음이 주어졌을 때:
#[cgp_component(AreaCalculator)]pub trait CanCalculateArea<Scalar> { fn area(&self) -> Scalar;}#[cgp_impl(new RectangleArea)]impl<Scalar> AreaCalculator<Scalar>where Scalar: Mul<Output = Scalar> + Copy,{ fn area( &self, #[implicit] width: Scalar, #[implicit] height: Scalar, ) -> Scalar { width * height }}
이제 하나의 블록에서 특정 인스턴스화를 위임하면서 동시에 검사할 수 있습니다:
delegate_and_check_components! { Rectangle { #[check_params(f64)] AreaCalculatorComponent: RectangleArea, }}
특정 컴포넌트의 검사를 생략하려면 #[skip_check]를 사용하세요:
delegate_and_check_components! { Rectangle { #[skip_check] AreaCalculatorComponent: RectangleArea, }}
이는 전용 check_components! 블록을 사용해 더 복잡한 검사를 수행하고 싶을 때 유용합니다.
Copy instead of Clone for owned getter field valuesRust 프로그래머는 소유 값을 함수 파라미터로 넘길 때 명시적인 .clone() 호출을 선호합니다. 이 원칙에 맞추기 위해, #[cgp_auto_getter]는 이제 반환되는 getter 값이 소유 값일 경우 Clone 대신 Copy를 요구합니다. 예를 들어:
#[cgp_auto_getter]pub trait RectangleFields: HasScalarType { fn width(&self) -> Self::Scalar; fn height(&self) -> Self::Scalar;}
추상 타입 Self::Scalar는 이제 getter 트레이트가 동작하려면 Copy를 구현해야 합니다. 같은 요구 사항이 #[implicit] 인자에도 적용됩니다:
#[cgp_fn]#[use_type(HasScalarType::Scalar)]pub fn rectangle_area( &self, #[implicit] width: Scalar, #[implicit] height: Scalar,) -> Scalar { width * height}
Copy 요구 사항은 값비싼 값이 소유된 암시적 인자로 암묵적으로 clone되는 잠재적 놀라움을 방지합니다.
{Type}Of type alias from #[cgp_type]#[cgp_type] 매크로는 더 이상 {Type}Of 형태의 타입 별칭을 생성하지 않습니다. 예를 들어 다음이 주어졌을 때:
#[cgp_type]pub trait HasScalarType { type Scalar;}
이 매크로는 이전에는 다음을 생성했습니다:
pub type ScalarOf<Context> = <Context as HasScalarType>::Scalar;
이 별칭은 원래 중첩 컨텍스트에서의 추상 타입을 돕기 위해 제공되었습니다. 하지만 새 #[use_type] 속성이 동일한 사용 사례에 대해 훨씬 더 나은 인체공학을 제공하므로, 더 이상 이 별칭이 사용될 것으로 기대되지 않습니다.
ProvideType to TypeProviderHasType CGP 트레이트는 #[cgp_type]가 보조 타입 프로바이더를 생성하는 데 내부적으로 사용됩니다. 그 프로바이더 트레이트는 이전에는 TypeComponent라는 컴포넌트를 가지는 ProvideType이라는 이름이었습니다:
#[cgp_component { name: TypeComponent, provider: ProvideType, derive_delegate: UseDelegate<Tag>,}]pub trait HasType<Tag> { type Type;}
v0.7.0에서는 프로바이더 이름을 TypeProvider로, 컴포넌트 이름을 TypeProviderComponent로 바꿉니다:
#[cgp_component { provider: TypeProvider, derive_delegate: UseDelegate<Tag>,}]pub trait HasType<Tag> { type Type;}
이는 #[cgp_type]가 확립한 컨벤션과 이름을 맞춥니다. 예를 들어 다음이 주어졌을 때:
#[cgp_type]pub trait HasScalarType { type Scalar;}
생성되는 프로바이더 이름은 ScalarTypeProvider이고 컴포넌트 이름은 ScalarTypeProviderComponent입니다.
CGP v0.7.0은 초기 릴리스 이후 라이브러리에서 가장 큰 인체공학 개선을 나타냅니다. #[cgp_fn], #[implicit], #[use_provider], #[use_type]의 조합은 CGP 코드에서 가장 흔한 보일러플레이트 원천 — getter 트레이트, 수동 where 절, Self:: 접두사 — 를 제거하면서도, 생성되는 코드를 완전히 투명하고 비용 0으로 유지합니다.
CGP가 처음이라면, 면적 계산 튜토리얼이 시작하기에 가장 좋은 곳입니다. 이 튜토리얼들은 일반 Rust 함수에서 출발해, 플러그 가능한 정적 디스패치를 갖춘 조합 가능한 컨텍스트 제네릭 프로바이더까지 전체 그림을 쌓아 올립니다.