-
Notifications
You must be signed in to change notification settings - Fork 8
Description
Problem Statement
A key challenge with implementing CGP providers today is that many developers are intimidated by the where clause in the provider trait. Although the where clause is very powerful to enable us to perform dependency injection and type equality constraints, the concept is too alien to most Rust programmers that their brain stop working when reading the where clause.
This task will fix the developer experience by transforming some where clause constraints into phantom associated types with the #[use] attribute. The hope is that this will make CGP provider code more readable, and align with the developers' existing understanding of using Rust traits.
#[use] Attribute
Suppose we have the following traits:
#[cgp_auto_getter]
pub trait HasName {
type Name;
fn name(&self) -> &Self::Name;
}
#[cgp_component(Greeter)]
pub trait CanGreet {
fn greet(&self);
}The #[use] attribute can be specified in the body of a #[cgp_impl] as follows:
#[cgp_impl(GreetHello)]
impl Greeter {
#[use(HasName)]
type Name: Display;
fn greet(&self) {
println!("Hello, {}!", self.name());
}
}Behind the scene, the #[use] attribute will be desugared into where clause, together with the constraint specified in the type. So it becomes:
#[cgp_impl(GreetHello)]
impl<Context> Greeter for Context
where
Context: HasName<Name: Display>,
{
fn greet(&self) {
println!("Hello, {}!", self.name());
}
}As we can see, the use of the associated type syntax look much simpler than the use of a generic where clause. This also makes it clear to the reader that there is an abstract Name type inside HasName, without requiring them to look it up. This also scales better when there are multiple associated types involved, as the user can specify them as multiple associated types.
We can also similarly use the #[use] attribute inside #[cgp_auto_impl]:
#[cgp_auto_impl]
pub trait CanGreetHello {
#[use(HasName)]
type Name = String;
fn greet(&self) {
println!("Hello, {}!", self.name());
}
}In the example, we also use the type equal syntax to impose a constraint in the associated type. With that, the code desugars to:
pub trait CanGreetHello: HasName<Name = String> {
fn greet(&self);
}
impl<Context> CanGreetHello for Context
where
Context: HasName<Name = String>,
{
fn greet(&self) {
println!("Hello, {}!", self.name());
}
}Non-Self Types
We should also able to extend the use of the #[use] macro, and apply them on associated types from other non-self types. For example, if the Greeter component is redefined as:
#[cgp_component(Greeter)]
pub trait CanGreet<Entity> {
fn greet(&self, entity: &Entity);
}Then we should be able to write something like:
#[cgp_impl(GreetHello)]
impl<Entity> Greeter<Entity> {
#[use(Entity: HasName)]
type Name: Display;
fn greet(self, entity: &Entity) {
println!("Hello, {}!", entity.name());
}
}