Replies: 3 comments
-
Hey @iMashtak, thank you for exploring practical use cases for CGP. Your SQL builder idea is a great example of where CGP can shine. It is well-suited for building a type-level DSL that constructs SQL queries. Building a full ORM from scratch can be ambitious, so starting with a smaller scope that handles basic SQL queries first would make the project more approachable and easier to iterate on. A particularly valuable direction is using dependency injection to validate SQL queries against the defined tables and columns in the application context. One of the strongest advantages CGP can offer here is type-safe handling of schema migrations. Because defining new contexts is cheap, you could define one context using the current schema and another using the post-migration schema. Then, the type system can enforce that queries intended to work across both schemas are indeed compatible. This approach would give Rust developers strong compile-time guarantees for evolving databases safely. I encourage you to start small and share your progress, even if the design is not perfect at first. Regular blog posts or updates can be very valuable, and I will try to provide feedback to help refine your approach. Sharing your journey with the Rust community is also helpful because contributions and examples from developers beyond the library author give a project much more credibility. Regarding your second idea about an application context, I am not very familiar with Spring, but I recognize that CGP can remind some readers of “enterprise” frameworks like it. There may be useful patterns to adapt from Spring, yet I would be cautious about directly imitating its style or making CGP appear as a Rust clone of those frameworks. One of the strengths of CGP is that it does not require a monolithic framework to support application development. Instead, it serves as a general foundation for implementing any application in a modular and type-safe way. My recommendation is to experiment with a few simple CGP patterns in an existing application and share your experience. For example, you could implement modular error handling, define a I also looked at your example code. The overall structure looks good. For the last part, you do not need a macro like #[cgp_provider]
impl<Context> FunctionCallArgsCollector<Context, Nil> for PseudoCode
where
Context:,
{
type ArgTypes = Nil;
fn collect_function_call_args(
context: &Context,
_code: PhantomData<Nil>,
collection: &mut Vec<String>,
) {
}
}
#[cgp_provider]
impl<Context, Head, Tail, HeadArg, TailArgs> FunctionCallArgsCollector<Context, Cons<Head, Tail>>
for PseudoCode
where
Context: CanBuildExpression<Head, Type = HeadArg>,
PseudoCode: FunctionCallArgsCollector<Context, Tail, ArgTypes = TailArgs>,
{
type ArgTypes = Cons<HeadArg, TailArgs>;
fn collect_function_call_args(
context: &Context,
_code: PhantomData<Cons<Head, Tail>>,
collection: &mut Vec<String>,
) {
collection.push(context.build_expression(PhantomData::<Head>));
PseudoCode::collect_function_call_args(context, PhantomData, collection)
}
} This approach gives you the flexibility of arbitrary-length argument lists without relying on additional macros. |
Beta Was this translation helpful? Give feedback.
-
@soareschen thank you for your response and ideas! I will continue working on
|
Beta Was this translation helpful? Give feedback.
-
You might hit recursion limit when using complex type-level composition, and usually it can be solved by increasing the recursion limit such as with To use #[cgp_component {
provider: ExpressionBuilder,
derive_delegate: UseDelegate<Code>,
}]
pub trait CanBuildExpression<Code> {
fn level(&self) -> u64;
fn build_expression(&self, code: PhantomData<Code>) -> String;
} Once you do that, you can define the providers separately in different modules, such as: #[cgp_new_provider]
impl<Context, Table, Alias, Name>
ExpressionBuilder<Context, FieldReferenceClause<Table, Alias, Name>> for BuildFindReferenceExpression
{ ... }
#[cgp_new_provider]
impl<Context, Name, Args> ExpressionBuilder<Context, FunctionCallClause<Name, Args>> for BuildFunctionCallExpression
{ ... } And then wire them to delegate_components! {
PseudoCode {
ExpressionBuilderComponent: UseDelegate<
new ExpressionBuilderComponents {
<Table, Alias, Name> FieldReferenceClause<Table, Alias, Name>:
BuildFindReferenceExpression,
<Left, Operator, Right> BinaryOperatorCallClause<Left, Operator, Right>:
BuildBinaryOpExpression,
<Content> IntegerClause<Content>:
BuildIntegerExpression,
<Content> StringClause<Content>:
BuildStringExpression,
<Name, Args> FunctionCallClause<Name, Args>:
BuildFunctionCallExpression,
}>
}
} You could also define delegate_components! {
new ExpressionBuilderComponents {
<Table, Alias, Name> FieldReferenceClause<Table, Alias, Name>:
BuildFindReferenceExpression,
<Left, Operator, Right> BinaryOperatorCallClause<Left, Operator, Right>:
BuildBinaryOpExpression,
<Content> IntegerClause<Content>:
BuildIntegerExpression,
<Content> StringClause<Content>:
BuildStringExpression,
<Name, Args> FunctionCallClause<Name, Args>:
BuildFunctionCallExpression,
}
}
delegate_components! {
PseudoCode {
ExpressionBuilderComponent: UseDelegate<ExpressionBuilderComponents>
}
} |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
It seems that CGP may be adopted only if it will give a real-world example of its usage.
First idea - SQL builder. Currently rust ecosystem have several libraries for working with SQL databases (sqlx, seaorm, diesel). Sqlx do not provide any query builder, seaorm and diesel have its own implementations that build sql query in runtime. My idea is to create universal SQL-builder that will be able to construct SQL query tree (via Rust type system) and then translate it to some library-defined format. So we could translate abstract SQL-type to text and get sqlx-compatible string or seaorm-object. CGP here plays two roles - it provide building blocks for walking through SQL-type and it give ability to choose resulting type of translation.
I played a bit with it: https://github.com/iMashtak/calia/blob/main/calia/examples/basic.rs (all that is above main function are "client" code, all that below are "library" code).
The only thing I did not done is typechecking of expressions in SQL-query. I wish to have an ability to check in compile time that sql-function accepts parameters of given types. Maybe I am missing something.
Second idea - application context. I have strong background in Java and Spring Framework (Spring Boot) and I find it very convenient to use its annotation-based approach to build application context and retrieve values from it. I tried something like this but did not made any stable version that satisfies me.
What do you think about these ideas? For me they may have high level of adoption.
Beta Was this translation helpful? Give feedback.
All reactions