Supercharge Rust functions with implicit arguments using CGP v0.7.0
CGP v0.7.0 has been released, bringing a major expansion to the CGP macro toolkit. The centerpiece of this release is a suite of new annotations — #[cgp_fn], #[implicit], #[uses], #[extend], #[use_provider], and #[use_type] — that let you write context-generic code in plain function syntax with dramatically less boilerplate than before.
What is CGP?
If you are new here, Context-Generic Programming (CGP) is a modular programming paradigm for Rust that unlocks powerful design patterns for writing code that is generic over a context (Self) type. CGP lets you define functions and implementations that work across many different context types without any manual boilerplate, all through Rust's own trait system and with zero runtime overhead.
Before diving into the specifics of this release, it is highly recommended that you read the new Area Calculation Tutorials, which walk through the motivation for CGP and the v0.7.0 features in far greater depth than this post can cover.
The problem: parameter threading and tight coupling
To understand why v0.7.0 matters, it helps to appreciate the two limitations in conventional Rust that motivated it.
The first is explicit parameter threading. When a plain Rust function needs to pass values to another function, every intermediate caller in the chain must accept those values as arguments and forward them explicitly — even if they do not use them directly. As call chains grow, function signatures accumulate parameters that exist purely to satisfy the requirements of their callees.
The second is tight coupling to a concrete context struct. Rust developers often address parameter threading by grouping values into a single struct and defining methods on it. This does clean up the call signatures, but it tightly couples an implementation to one specific type. When the struct grows or needs to be extended, everything referencing it is affected, and there is no clean way to have multiple independent contexts share the same method without duplicating code.
CGP's #[cgp_fn] macro and #[implicit] arguments, introduced in v0.7.0, address both of these problems at once.
Define CGP functions using the #[cgp_fn] macro
The centerpiece of v0.7.0 is the #[cgp_fn] macro, which lets us write context-generic code in plain function syntax. A function decorated with #[cgp_fn] accepts a &self parameter that refers to a generic context, and may mark any of its arguments with #[implicit] to indicate that those values should be automatically extracted from the context rather than passed by the caller.
For example, here is how we define a context-generic function that computes the area of a rectangle:
#[cgp_fn]
pub fn rectangle_area(
&self,
#[implicit] width: f64,
#[implicit] height: f64,
) -> f64 {
width * height
}
Three annotations do the work here. #[cgp_fn] augments the plain function and turns it into a context-generic capability. &self provides a reference to whatever context this function is called on. And #[implicit] on both width and height tells CGP to fetch those values automatically from &self instead of requiring the caller to supply them.
The function body itself is entirely conventional Rust — there are no new concepts to learn beyond the annotations.
To use this function on a concrete type, we define a minimal context and apply #[derive(HasField)] to enable generic field access on it:
#[derive(HasField)]
pub struct PlainRectangle {
pub width: f64,
pub height: f64,
}
The #[derive(HasField)] macro generates implementations that allow CGP to access the fields of PlainRectangle generically by field name. With that in place, we can call rectangle_area as a method:
let rectangle = PlainRectangle {
width: 2.0,
height: 3.0,
};
let area = rectangle.rectangle_area();
assert_eq!(area, 6.0);
That's it. CGP propagates the fields to the function arguments automatically. You do not need to write any implementation for PlainRectangle beyond deriving HasField.
Importing other CGP functions with #[uses]
One of the most valuable properties of context-generic functions is their ability to compose with each other. The #[uses] attribute allows a CGP function to import another CGP function as a dependency, so that it can call it on self without the caller needing to know anything about the imported function's own requirements.
For example, here is how we define scaled_rectangle_area, which calls rectangle_area internally:
#[cgp_fn]
#[uses(RectangleArea)]
pub fn scaled_rectangle_area(
&self,
#[implicit] scale_factor: f64,
) -> f64 {
self.rectangle_area() * scale_factor * scale_factor
}
The #[uses(RectangleArea)] attribute imports the RectangleArea trait — the CamelCase name that #[cgp_fn] derives from the function name rectangle_area. We only need to declare scale_factor as an implicit argument, since width and height are already consumed internally by rectangle_area.
With scaled_rectangle_area defined, we can introduce a second context that adds a scale_factor field:
#[derive(HasField)]
pub struct ScaledRectangle {
pub scale_factor: f64,
pub width: f64,
pub height: f64,
}
Like PlainRectangle, only #[derive(HasField)] is needed. Both contexts can now coexist independently:
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);
Importantly, PlainRectangle is never modified. It continues to support rectangle_area on its own, and scaled_rectangle_area is available only on contexts that also carry a scale_factor field. Two independent contexts can share the same function definitions without either one knowing about the other.
Re-exporting imported CGP functions with #[extend]
The #[uses] attribute is analogous to Rust's use statement for importing module constructs. This means that the imported CGP functions are hidden behind the generated where bounds using impl-side dependencies.
The #[extend] attribute lets you import and re-export another CGP function, so that it is available to anyone who imports your function. This works similarly to Rust's pub use for re-exporting module constructs.
For example, we can rewrite scaled_rectangle_area to use #[extend] instead of #[uses]:
#[cgp_fn]
#[extend(RectangleArea)]
pub fn scaled_rectangle_area(
&self,
#[implicit] scale_factor: f64,
) -> f64 {
self.rectangle_area() * scale_factor * scale_factor
}
This means that any construct that imports ScaledRectangleArea now also has access to RectangleArea. For example:
#[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(),
);
}
The print_scaled_rectangle_area function only needs to import ScaledRectangleArea, yet it can call both rectangle_area and scaled_rectangle_area on self.
Using #[implicit] in #[cgp_impl]
CGP v0.7.0 also brings support for using #[implicit] arguments inside #[cgp_impl], which is used to write named provider implementations for CGP components. This is especially useful when implementing traits defined with #[cgp_component].
For example, here is how we define an AreaCalculator component and a named provider for it using implicit arguments:
#[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
}
}
Prior to v0.7.0, achieving the same result required defining a separate getter trait with #[cgp_auto_getter], adding it to the provider's where clause, and calling its getter methods explicitly:
#[cgp_auto_getter]
pub trait HasRectangleFields {
fn width(&self) -> f64;
fn height(&self) -> f64;
}
#[cgp_impl(new RectangleAreaCalculator)]
impl AreaCalculator
where
Self: HasRectangleFields,
{
fn area(&self) -> f64 {
self.width() * self.height()
}
}
With #[implicit], that entire layer of boilerplate disappears. The width and height values are fetched directly from the context, and there is no need to manually maintain a getter trait, a where clause, or individual method calls. Behind the scenes, #[implicit] in #[cgp_impl] is semantically equivalent to #[cgp_auto_getter] and is equally zero cost.
Provider composition with #[use_provider]
CGP v0.7.0 also introduces the #[use_provider] attribute for ergonomic import of other providers inside higher-order provider implementations. This is particularly useful when building providers that delegate part of their computation to a pluggable inner provider.
For example, suppose we want a general ScaledAreaCalculator that wraps any inner AreaCalculator provider and applies a scale factor to its result. We can now write this as follows:
#[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
}
}
The #[use_provider] attribute declares that InnerCalculator must implement the AreaCalculator provider trait. Before this attribute was available, we had to write the same constraint manually in the where clause with an explicit Self parameter:
#[cgp_impl(new ScaledAreaCalculator<InnerCalculator>)]
impl<InnerCalculator> AreaCalculator
where
InnerCalculator: AreaCalculator<Self>,
{
fn area(&self, #[implicit] scale_factor: f64) -> f64 {
InnerCalculator::area(self) * scale_factor * scale_factor
}
}
The main ergonomic improvement is that #[use_provider] automatically inserts Self as the first generic parameter to the provider trait, so you can treat provider traits the same way as consumer traits without needing to understand the underlying difference. The provider can then be composed into any context via delegate_components!:
delegate_components! {
ScaledRectangle {
AreaCalculatorComponent:
ScaledAreaCalculator<RectangleAreaCalculator>,
}
}
This shows that CGP providers are just plain Rust types, and higher-order providers like ScaledAreaCalculator<RectangleAreaCalculator> are simply generic type instantiations. No new runtime concepts are involved.
Abstract type import with #[use_type]
CGP v0.7.0 also introduces the #[use_type] attribute for ergonomic import of abstract associated types. This lets you write context-generic functions that work with abstract types — such as a Scalar type that might be f32, f64, or any other numeric type — without needing to write Self:: prefixes everywhere.
For example, here is how we define a version of rectangle_area that is generic over any scalar type by importing the Scalar associated type from a HasScalarType trait:
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
}
Without #[use_type], the same function would require Self::Scalar throughout, which is noisier. Under the hood, #[use_type(HasScalarType::Scalar)] desugars to #[extend(HasScalarType)] and rewrites all references to the bare Scalar identifier back to Self::Scalar:
#[cgp_fn]
#[extend(HasScalarType)]
pub fn rectangle_area(
&self,
#[implicit] width: Self::Scalar,
#[implicit] height: Self::Scalar,
) -> Self::Scalar {
width * height
}
We can now define context types that use different scalar types. For example, here is a rectangle that uses f32 instead of f64:
#[derive(HasField)]
pub struct F32Rectangle {
pub width: f32,
pub height: f32,
}
impl HasScalarType for F32Rectangle {
type Scalar = f32;
}
And rectangle_area() will work seamlessly with f32 values:
let f32_rectangle = F32Rectangle {
width: 3.0,
height: 4.0,
};
assert_eq!(f32_rectangle.rectangle_area(), 12.0);
The #[use_type] attribute is also supported in both #[cgp_component] and #[cgp_impl], making it uniformly available across the entire CGP surface:
#[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
}
}
"Isn't this just Scala implicits?"
The word "implicit" may raise a flag for developers familiar with Scala's implicit parameter system — a feature with a well-documented reputation for producing confusing errors, ambiguous resolution, and code that is hard to trace. It's a fair concern, and it deserves a direct answer: CGP's #[implicit] attribute shares the same surface-level motivation as Scala implicits (reducing boilerplate at call sites), but the underlying mechanisms are categorically different in the ways that matter most.
Resolution scope. In Scala, the compiler searches a broad, layered implicit scope that spans local variables, companion objects, and imports — meaning an implicit value can materialize from almost anywhere. In CGP, #[implicit] always resolves to a field on self, and nowhere else. There is no ambient environment, no companion object search, and no imports to reason about.
No ambiguity. Scala's type-only resolution means two in-scope values of the same type create an ambiguity that requires explicit disambiguation. CGP resolves by both name and type: #[implicit] width: f64 looks for a field named specifically width of type f64. Because Rust structs cannot have two fields with the same name, CGP implicit arguments are unambiguous by construction.
No propagation. Scala's implicit requirements climb the call stack — any function that calls an implicit-consuming function must either declare the same implicit parameter or supply the value explicitly. CGP has no such propagation. The HasField bounds generated by #[implicit] appear in the provider's where clause and are fully encapsulated there. Callers see only the consumer trait; they never need to know which fields the implementation reads internally.
Transparent desugaring. Every #[implicit] annotation expands mechanically into a HasField trait bound and a get_field call — ordinary Rust constructs that any developer can read and verify. There is no hidden resolution phase, no special compiler magic, and no "implicit hell" accumulation risk.
New area calculation tutorials
To accompany this release, two new area calculation tutorials have been published that build up the full CGP feature set from first principles.
The Context-Generic Functions tutorial starts from plain Rust and introduces #[cgp_fn], #[implicit], and #[uses]. It walks through the full desugaring of rectangle_area into Rust traits and blanket implementations, explains the HasField-based zero-cost field access model, and compares CGP's implicit arguments to Scala's implicit parameters for readers coming from other ecosystems.
The Static Dispatch tutorial introduces a second shape — the circle — to motivate a unified CanCalculateArea interface. It demonstrates Rust's coherence restrictions as a concrete problem, then resolves them using #[cgp_component] and named providers defined with #[cgp_impl]. Finally, it covers delegate_components! for configurable static dispatch and #[use_provider] for composing higher-order providers.
Both tutorials are designed to be read sequentially and assume no prior knowledge of CGP beyond basic Rust familiarity.
New CGP skills for LLMs
CGP v0.7.0 ships with preliminary support for agent skills for LLMs. The CGP Skills document is specifically written to teach LLMs about CGP in a compact way.
If you would like to try out CGP with the assistance of an LLM, we recommend including the CGP skill in your prompts so that you can ask it to clarify any CGP concept.
Breaking changes
v0.7.0 includes several minor breaking changes. The vast majority of existing CGP code is unaffected; the sections below describe what to look for and how to migrate.
Removal of #[cgp_context]
The #[cgp_context] macro has been removed, following its deprecation in v0.6.0. It is now idiomatic to define context types directly without any additional CGP macro applied to them.
Affected code can follow the migration guide in the v0.6.0 post to use the context type for delegation directly, instead of through a {Context}Components delegation table.
Change of consumer trait blanket implementation
The blanket implementation of consumer traits generated by #[cgp_component] has been simplified. For example, given:
#[cgp_component(Greeter)]
pub trait CanGreet {
fn greet(&self);
}
The generated blanket implementation is now:
impl<Context> CanGreet for Context
where
Context: Greeter<Context>,
{
fn greet(&self) {
Context::greet(self)
}
}
That is, a Context type implements the consumer trait if it also implements the provider trait with itself as the context type.
Prior to this, the blanket implementation involved an additional table lookup similar to the provider trait:
impl<Context> CanGreet for Context
where
Context: DelegateComponent<GreeterComponent>,
Context::Delegate: Greeter<Context>,
{
fn greet(&self) {
Context::Delegate::greet(self)
}
}
Since the provider trait's blanket implementation already performs the DelegateComponent lookup, the consumer trait no longer needs to repeat it. This also introduces the nice property that a provider trait implementation can satisfy the consumer trait directly, which may be useful in niche cases where a context acts as its own provider.
A consequence of this change is that when both the consumer trait and provider trait are in scope, there may be ambiguity when calling static methods on the context. Because a context that implements a consumer trait through delegate_components! is also its own provider, Rust cannot determine which trait implementation to use without an explicit self receiver. Calls through self are unaffected.
Syntax improvements for check_components! and delegate_and_check_components!
With the removal of #[cgp_context], it is now idiomatic to always build the delegate lookup table directly on the context type. The check_components! and delegate_and_check_components! macros have been updated accordingly.
Implicit check trait name
The check trait name can now be omitted:
delegate_and_check_components! {
ScaledRectangle {
AreaCalculatorComponent:
ScaledAreaCalculator<RectangleAreaCalculator>,
}
}
check_components! {
ScaledRectangle {
AreaCalculatorComponent,
}
}
By default, the macros generate a check trait named __CanUse{Context}. The name can be overridden with a #[check_trait] attribute:
delegate_and_check_components! {
#[check_trait(CanUseScaledRectangle)]
ScaledRectangle {
AreaCalculatorComponent:
ScaledAreaCalculator<RectangleAreaCalculator>,
}
}
check_components! {
#[check_trait(CheckScaledRectangle)]
ScaledRectangle {
AreaCalculatorComponent,
}
}
The following old syntax is no longer valid:
delegate_and_check_components! {
CanUseScaledRectangle for ScaledRectangle;
ScaledRectangle {
AreaCalculatorComponent:
ScaledAreaCalculator<RectangleAreaCalculator>,
}
}
check_components! {
CheckScaledRectangle for ScaledRectangle {
AreaCalculatorComponent,
}
}
The reason for the change is that it is simpler to parse an optional attribute at the start of a macro invocation than an optional name before a for keyword. The #[check_trait] syntax is both easier to implement and more consistent with how other CGP macros accept optional configuration.
Specifying check params using #[check_params] in delegate_and_check_components!
The delegate_and_check_components! macro now supports #[check_params] for CGP components that carry generic parameters. For example, given:
#[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
}
}
You can now both delegate and check a specific instantiation in one block:
delegate_and_check_components! {
Rectangle {
#[check_params(f64)]
AreaCalculatorComponent:
RectangleArea,
}
}
To skip checking a particular component, use #[skip_check]:
delegate_and_check_components! {
Rectangle {
#[skip_check]
AreaCalculatorComponent:
RectangleArea,
}
}
This is useful when you prefer to perform more complex checks using a dedicated check_components! block.
Use Copy instead of Clone for owned getter field values
Rust programmers prefer explicit .clone() calls when passing owned values to function parameters. To align with this principle, #[cgp_auto_getter] now requires Copy instead of Clone when the returned getter values are owned. For example:
#[cgp_auto_getter]
pub trait RectangleFields: HasScalarType {
fn width(&self) -> Self::Scalar;
fn height(&self) -> Self::Scalar;
}
The abstract type Self::Scalar must now implement Copy for the getter trait to work. The same requirement applies to #[implicit] arguments:
#[cgp_fn]
#[use_type(HasScalarType::Scalar)]
pub fn rectangle_area(
&self,
#[implicit] width: Scalar,
#[implicit] height: Scalar,
) -> Scalar {
width * height
}
The Copy requirement prevents potential surprises when an expensive value is implicitly cloned into an owned implicit argument.
Removal of {Type}Of type alias from #[cgp_type]
The #[cgp_type] macro no longer generates a type alias in the {Type}Of form. For example, given:
#[cgp_type]
pub trait HasScalarType {
type Scalar;
}
The macro would previously generate:
pub type ScalarOf<Context> = <Context as HasScalarType>::Scalar;
This alias was originally provided to assist with abstract types in nested contexts. The new #[use_type] attribute offers significantly better ergonomics for those same use cases, so the aliases are no longer expected to be used.
Rename ProvideType to TypeProvider
The HasType CGP trait is used internally by #[cgp_type] to generate helper type providers. Its provider trait was previously named ProvideType with a component named TypeComponent:
#[cgp_component {
name: TypeComponent,
provider: ProvideType,
derive_delegate: UseDelegate<Tag>,
}]
pub trait HasType<Tag> {
type Type;
}
v0.7.0 renames the provider to TypeProvider and the component to TypeProviderComponent:
#[cgp_component {
provider: TypeProvider,
derive_delegate: UseDelegate<Tag>,
}]
pub trait HasType<Tag> {
type Type;
}
This brings the naming in line with the convention established by #[cgp_type]. For example, given:
#[cgp_type]
pub trait HasScalarType {
type Scalar;
}
The generated provider name is ScalarTypeProvider and the component name is ScalarTypeProviderComponent.
Getting started with v0.7.0
CGP v0.7.0 represents the most significant ergonomics improvement to the library since its initial release. The combination of #[cgp_fn], #[implicit], #[use_provider], and #[use_type] removes the most common sources of boilerplate in CGP code — getter traits, manual where clauses, and Self:: prefixes — while keeping the generated code fully transparent and zero cost.
If you are new to CGP, the Area Calculation Tutorials are the best place to start. They build up the full picture from plain Rust functions all the way to composable, context-generic providers with pluggable static dispatch.
