Skip to content

CGP Getter Improvements#94

Merged
soareschen merged 14 commits intomainfrom
getter-combinators
May 5, 2025
Merged

CGP Getter Improvements#94
soareschen merged 14 commits intomainfrom
getter-combinators

Conversation

@soareschen
Copy link
Collaborator

@soareschen soareschen commented May 5, 2025

Summary

This PR brings several improvements to the CGP getters, making them more usable for more advanced uses.

MapField

A new blanket trait MapField has been introduced to help with type inference when chaining field accessors:

pub trait MapField<Tag>: HasField<Tag> {
    fn map_field<T>(
        &self,
        _tag: PhantomData<Tag>,
        mapper: impl for<'a> FnOnce(&'a Self::Value) -> &'a T,
    ) -> &T;
}

impl<Context, Tag> MapField<Tag> for Context
where
    Context: HasField<Tag>,
    Tag: 'static,
{
    fn map_field<T>(
        &self,
        tag: PhantomData<Tag>,
        mapper: impl for<'a> FnOnce(&'a Self::Value) -> &'a T,
    ) -> &T {
        mapper(self.get_field(tag))
    }
}

The general problem is as follows: when a field value is used as an intermediary to get an inner value, Rust would fail to infer the lifetime of the intermediary value, and thus requiring it to have a 'static lifetime.

For example, a generic implementation of context.get_field(PhantomData::<symbol!("foo")>).get_field(PhantomData::<symbol!("bar")>) would require <Context as HasField<symbol!("foo")>>::Value: 'static. However, with MapField, we can rewrite the expression as context.map_field(PhantomData::<symbol!("foo")>, |inner| inner.get_field(PhantomData::<symbol!("bar")>)), and the lifetime inference will be fixed.

Relaxed HasField implementation for Deref

The HasField trait has a blanket implementation for context types that implement Deref. Previously, the blanket implementation required Target: 'static due to the same lifetime issues mentioned earlier.

By introducing a DerefMap helper trait similar to MapField, we have managed to remove the 'static bound, and allows the blanket implementation to also work on types that contain non-static lifetimes.

Auto infer provider name in #[cgp_getter]

Previously, at minimal the provider name must always be specified when defining getter traits using #[cgp_getter]. For example:

#[cgp_getter {
    provider: NameGetter,
}]
pub trait HasName {
    fn name(&self) -> &str;
}

This requirement has been relaxed if the getter trait name follows the convention Has{FieldName}. In such cases, the provider name is automatically set to {FieldName}Getter. So the same example can be simplified to:

#[cgp_getter]
pub trait HasName {
    fn name(&self) -> &str;
}

Note that if the getter trait name does not start with "Has", then a provider name must still be explicitly specified.

ChainGetters

A new ChainGetters provider has been implemented to allow chaining nested getters together to form a single FieldGetter. The implementation makes use of MapField to overcome the lifetime errors, that would have required the intermediary value type to be 'static. This can be useful for implementing chain getters for values nested within the sub-fields of a context.

Following is an example context where the listen_port field is nested within sub-config types:

#[cgp_getter]
pub trait HasListenPort {
    fn listen_port(&self) -> &u16;
}

#[cgp_context(MyContextComponents)]
#[derive(HasField)]
pub struct MyContext {
    pub config: Config,
}

#[derive(HasField)]
pub struct Config {
    pub network: NetworkConfig,
}

#[derive(HasField)]
pub struct NetworkConfig {
    pub listen_port: u16,
}

delegate_components! {
    MyContextComponents {
        ListenPortGetterComponent:
            WithProvider<ChainGetters<Product! [
                UseField<symbol!("config")>,
                UseField<symbol!("network")>,
                UseField<symbol!("listen_port")>,
            ]>>
    }
}

Note that since ChainGetters is a non-standard FieldGetter implementation, it can only be used by #[cgp_getter] traits through the WithProvider wrapper provider.

@soareschen soareschen merged commit 4c5e82a into main May 5, 2025
5 checks passed
@soareschen soareschen deleted the getter-combinators branch May 5, 2025 18:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant