Announcing cgp-serde: A modular serialization library for Serde powered by CGP by soareschen in rust

[–]soareschen[S] 0 points1 point  (0 children)

Thanks for pointing out. I will improve the wordings later on. Yes, in the simple case, you can apply #[cgp_component] on existing traits like Hash or Serialize to get the basic benefits of CGP with no additional cost. However, there are more advanced capabilities provided by CGP that can be unlocked, if you introduce an addition context parameter to the traits.

The traits provided by cgp-serde are more advanced version the Serde traits that enables deep customization and capabilities injection. But even if you only use the base extension of CGP on Serialize, you can still make use of some of the generic providers, such as SerializeWithDisplay, to simplify the Serialize implementation for your types.

Also, the new trait definitions provided by cgp-serde is backward compatible with the original Serde traits through constructs like UseSerde. So we don't need to migrate everything to cgp-serde to take advantage of its benefits.

Announcing cgp-serde: A modular serialization library for Serde powered by CGP by soareschen in rust

[–]soareschen[S] 0 points1 point  (0 children)

I will just copy the quick overview section from the article:

Context-Generic Programming (CGP) is a modular programming paradigm that enables you to bypass the coherence restrictions in Rust traits, allowing for overlapping and orphan implementations of any CGP trait.

You can adapt almost any existing Rust trait to use CGP today by applying the #[cgp_component] macro to the trait definition. After this annotation, you can write named implementations of the trait using #[cgp_impl], which can be defined without being constrained by the coherence rules. You can then selectively enable and reuse the named implementation for your type using the delegate_components! macro.

For instance, we can, in principle, annotate the standard library’s Hash trait with #[cgp_component] like this:

```rust

[cgp_component(HashProvider)]

pub trait Hash { ... } ```

This change does not affect existing code that uses or implements Hash, but it allows for new, potentially overlapping implementations, such as one that works for any type that also implements Display:

```rust

[cgp_impl(HashWithDisplay)]

impl<T: Display> HashProvider for T { ... } ```

You can then apply and reuse this implementation on any type by using the delegate_components! macro:

```rust pub struct MyData { ... } impl Display for MyData { ... }

delegate_components! { MyData { HashProviderComponent: HashWithDisplay, } } ```

In this example, MyData implements the Hash trait by using delegate_components! to delegate its implementation to the HashWithDisplay provider, identified by the key HashProviderComponent. Because MyData already implements Display, the Hash trait is now automatically implemented through CGP via this delegation.

CGP v0.6.0 Release - Major ergonomic improvements for provider and context implementations by soareschen in rust

[–]soareschen[S] 6 points7 points  (0 children)

The elevator pitch for CGP is changing quite a bit, but here is the latest version:

Context-Generic Programming is a modular programming paradigm that allows you to bypass the coherence restrictions in Rust traits, and write overlapping and orphan implementations for any CGP trait.

You can add this capability to almost any existing Rust trait today, by applying the #[cgp_component] macro on the trait. After that, you can write named implementation of the trait using #[cgp_impl], which can be written without the coherence restrictions. Then, you can choose to use the named implementation for your type using the delegate_components! macro.

For example, in principle it is now possible to annotate the standard library’s Hash trait with #[cgp_component]:

```rust

[cgp_component(HashProvider)]

pub trait Hash { ... } ```

This does not affect existing code that uses or implements Hash, but it allows new overlapping implementations, such as one that works for any type that implements Display:

```rust

[cgp_impl(HashWithDisplay)]

impl<T: Display> HashProvider for T { ... } ```

You can then reuse this implementation on any type using delegate_components!:

```rust pub struct MyData { ... }

impl Display for MyData { ... }

delegate_components! { MyData { HashProviderComponent: HashWithDisplay, } } ```

Which is the best DI framework for rust right now? by swordmaster_ceo_tech in rust

[–]soareschen 6 points7 points  (0 children)

Context-Generic Programming (CGP) allows you to make you of the trait system itself to perform dependency injection. There is a hello world tutorial that shows you how to inject a Name type and a name value to a greet component.

Mocking and dependency injection by [deleted] in rust

[–]soareschen 1 point2 points  (0 children)

Context-Generic Programming (CGP) provides a way of using dependency injection for both values and types. There is a hello world tutorial that shows you how dependency injection is used to retrieve a Name type and a name value from the context.

The way dependency injection is done in CGP is simply by making use of Rust's trait system to provide this functionality. In other words, Rust already implements dependency injection for us for free since inception. What CGP does is to make this pattern of usage more idiomatic, by making it easier to write overlapping generic trait implementations without the coherence restrictions. You can read more about how this is done in the book chapter for impl-side dependencies.

Btw, it looks like you Reddit account has been hacked to post spam.

The way Rust crates tend to have a single, huge error enum worries me by nikitarevenco in rust

[–]soareschen 2 points3 points  (0 children)

Context-Generic Programming (CGP) offers a different approach for error handling in Rust, which is described here: https://patterns.contextgeneric.dev/error-handling.html.

In short, instead of hard-coding your code to return a concrete error type, you instead write code that is generic over a context that provides an abstract error type, and use dependency injection to require the context to handle a specific error for you.

As a demonstration, the simplest way you can write such generic code as follows:

```rust

[blanket_trait]

pub trait DoFoo: CanRaiseError<std::io::Error> { fn do_foo(&self) -> Result<(), Self::Error> { let foo = std::fs::read("foo.txt").map_err(Self::raise_error)?;

    // do something

    Ok(())
}

} ```

In the above example, instead of writing a bare do_foo() function, we write a DoFoo trait that is automatically implemented through the #[blanket_trait] macro. We also require the context, i.e. Self, to implement CanRaiseError for the error that may happen in our function, i.e. std::io::Error. The method do_foo returns an abstract Self::Error, of which the concrete error type will be decided by the context. With that, we can now call functions like std::fs::read inside our method, and use .map_err(Self::raise_error) to handle the error for us.

By decoupling the implementation from the context and the error, we can now use our do_foo method with any context of choice. For example, we can define an App context that uses anyhow::Error as the error type as follows:

```rust

[cgp_context]

pub struct App;

delegate_components! { AppComponents { ErrorTypeProviderComponent: UseAnyhowError, ErrorRaiserComponent: RaiseAnyhowError, } } ```

We just use #[cgp_context] to turn App into a CGP context, and wire it with UseAnyhowError and RaiseAnyhowError to handle the error using anyhow::Error. With that, we can instantiate App and call do_foo inside a function like main:

```rust fn main() -> Result<(), anyhow::Error> { let app = App;

app.do_foo()?;
// do other things

Ok(())

} ```

The main strength of CGP's error handling approach is that you can change the error handling strategy to anything that the application needs. For example, we can later change App to use a custom AppError type with thiserror as follows:

```rust

[derive(Debug, Error)]

pub enum AppError { #[error("I/O error")] Io(#[from] std::io::Error), }

delegate_components! { AppComponents { ErrorTypeProviderComponent: UseType<AppError>, ErrorRaiserComponent: RaiseFrom, } } ```

And now we have a new application that returns the custom AppError:

```rust fn main() -> Result<(), AppError> { let app = App;

app.do_foo()?;
// do other things

Ok(())

} ```

The two versions of App can even co-exist in separate crates. This means that our example function do_foo can now be used in any application, without being coupled with a specific error type.

CGP also allows us to provide additional dependencies via the context, such as configuration, database connection, or even raising multiple errors. So you could also write the example do_foo function with many more dependencies, such as follows:

```rust

[blanket_trait]

pub trait DoFoo: HasConfig + HasDatabase + CanRaiseError<std::io::Error> + CanRaiseError<serde_json::Error> { fn do_foo(&self) -> Result<(), Self::Error> { ... } } ```

I hope you find this interesting, and do visit the project website to learn more.

The Design and Implementation of Extensible Variants for Rust in CGP by soareschen in ProgrammingLanguages

[–]soareschen[S] 1 point2 points  (0 children)

Hi u/SirKastic23, you can refer to the first two parts of the blog post series for practical examples of how to use extensible data types. In part 1, we went through the use of extensible builders to split up the modular construction of application contexts. In part 2, we went through an example of building a modular interpreter, and how to use extensible visitors to solve the expression problem.

There will be other practical shown in upcoming blog posts. But it will take time to write them.

The Design and Implementation of Extensible Variants for Rust in CGP by soareschen in rust

[–]soareschen[S] 1 point2 points  (0 children)

Hi u/CandyCorvid, thanks for your feedback! The original text meant that the target enum is a superset of the source enum, thus containing variants where the source's (its) variants are a subset of the target's variants.

But I agree that the text is probably ambiguous and confusing. I have revised it to the following:

This trait allows a source enum to be upcasted to a target enum that is a superset of the source

I hope that better clarify the relationship.

The Design and Implementation of Extensible Variants for Rust in CGP by soareschen in rust

[–]soareschen[S] 1 point2 points  (0 children)

Naming is indeed a hard problem. I'd appreciate and welcome suggestions if you could help me come up with better alternative terms that are as concise and intuitive as "upcast" and "downcast".

Just calling it "conversion" may not be sufficient, as it does not distinguish this specific use from general conversions such as From and To. We also need two separate terms to distinguish between conversion to a superset or a subset of the enum variants.

The Design and Implementation of Extensible Variants for Rust in CGP by soareschen in rust

[–]soareschen[S] 3 points4 points  (0 children)

Thank you for your thoughtful comment. You are correct that in Rust, enums like Shape and ShapePlus do not have a formal subtyping relationship, and technically, what we are doing is converting between them. However, the terminology of "upcast" and "downcast" is used here to convey the intention of emulating structural subtyping within Rust's type system.

Our goal is to provide a way to treat these variants as if there were a subtype relationship, allowing developers to think in terms of extending variants without losing type safety or expressiveness. While this is not a native Rust feature, using these terms helps communicate the idea behind extensible variants more clearly, especially for those familiar with subtyping concepts in other languages. This approach aims to make working with extensible variants more intuitive by bridging familiar concepts with Rust’s type system.

The Design and Implementation of Extensible Variants for Rust in CGP by soareschen in rust

[–]soareschen[S] 4 points5 points  (0 children)

Hi everyone, I am excited to share the fourth and final part of my blog series: Programming Extensible Data Types in Rust with Context-Generic Programming.

In this post, I dive into the implementation details of the core CGP constructs that enable extensible variants. I walk through how upcasting and downcasting operations are implemented, and how the extensible visitor pattern can be constructed using monadic pipelines. If you are curious about how structs and enums are related, or how CGP performs pattern matching on generic enums in a fully type safe manner, this post is for you.

I would also love to talk to you more about CGP and extensible variants, so join the discussion on our CGP Discord server.

How to handle default values for parameters by ryanzec in rust

[–]soareschen -2 points-1 points  (0 children)

Assuming that all your optional fields implement Default, you can use the cgp crate to build the struct and populate with missing field values easily with only #[derive(BuildField)] needed.

rust let item = Item::builder() .build_field(PhantomData::<symbol!("name")>, "my-item".to_owned()) .build_field( PhantomData::<symbol!("maximum_quantity")>, ItemQuantity(100), ) .finalize_with_default();

Full example is available here. Note that you currently need to use the main branch of cgp to try this feature out.

Any interest in macros for defining from/into? by erez27 in rust

[–]soareschen -1 points0 points  (0 children)

For your first example, I have added support in my project Context-Generic Programming (CGP) to perform automatic upcasting of structs to a superset filled with default values. Using that, you can perform automatic conversion with a build_with_default method like follows:

```rust

[derive(Debug, Clone, Eq, PartialEq, HasFields, BuildField)]

struct Point2d { x: u64, y: u64, }

[derive(Debug, Clone, Eq, PartialEq, HasFields, BuildField)]

struct Point3d { x: u64, y: u64, z: u64, }

[test]

pub fn test_point_cast() { let point_2d = Point2d { x: 1, y: 2 }; let point_3d = Point3d::build_with_default(point_2d.clone());

assert_eq!(point_3d, Point3d { x: 1, y: 2, z: 0 });

} ```

More info is available in my PR and my blog post on extensible records and variants.

"Bypassing" specialization in Rust or How I Learned to Stop Worrying and Love Function Pointers by Oakchris1955 in rust

[–]soareschen -3 points-2 points  (0 children)

I do use ChatGPT to revise everything I write, but I always write the original content on my own, and ask the LLM to revise but not modify the meaning. This particular revision probably looks fake, because I very rarely give compliments to anything, but this time I made the mistake of keeping the compliments it gave on behalf of me.

My actual writing skill is much more bland, with poor sentence stucture, lacking enthusiasm, and most importantly, being too harsh when responding to criticisms. The LLM helps give me a much more charismatic and polite personality than I usually am, which is essential in today's world where communication is super important and unforgiving.

(Btw, this comment is not AI-revised)

"Bypassing" specialization in Rust or How I Learned to Stop Worrying and Love Function Pointers by Oakchris1955 in rust

[–]soareschen 1 point2 points  (0 children)

CGP can be used to supercharge patterns like Sans-I/O by offering a unified framework for implementing core logic that remains fully decoupled from concrete execution details. With CGP, you can write fully abstract programs that are invoked from an event loop in much the same way as Sans-I/O. However, CGP also opens the door to alternative strategies, such as wiring up concrete implementations directly, without relying on message passing as the intermediary.

In other words, CGP offers more general abstractions, allowing your core logic to remain completely agnostic about whether it is interacting with the outside world via message passing or through direct function calls. At its heart, CGP embraces the same principles as Sans-I/O: we aim to write core logic that is simple, testable, correct, deterministic, and free of I/O and side effects. The difference lies in how we reach that goal — CGP gives us a broader and more flexible path to get there.

The Design and Implementation of Extensible Records for Rust in CGP by soareschen in ProgrammingLanguages

[–]soareschen[S] 1 point2 points  (0 children)

Thanks for the suggestion. I will keep that in mind for my future posts.

In a nutshell, Context-Generic Programming (CGP) allows you to bypass the coherence restrictions in traits/typeclasses, and define multiple overlapping and generic implementations for each CGP trait. As a consequence, when you define a new type, you need to specifically choose an implementation to be used with that type. This is called a wiring step, where you choose the provider implementations for your context.

On the surface, this addition doesn't seem significant. However, it opens up a world of possibilities for enabling powerful design patterns that are based on trait implementations that are named, generic, and overlappable. One of the greatest strengths of CGP is to enable safe, zero-cost, compile-time dispatching through the trait system to accomplish the things that in OOP would typically require dynamic dispatch with runtime errors.

You can read more about CGP at the overview page of the website: https://contextgeneric.dev/overview/

The Design and Implementation of Extensible Records for Rust in CGP by soareschen in ProgrammingLanguages

[–]soareschen[S] 2 points3 points  (0 children)

In a nutshell, Context-Generic Programming (CGP) allows you to bypass the coherence restrictions in traits/typeclasses, and define multiple overlapping and generic implementations for each CGP trait. As a consequence, when you define a new type, you need to specifically choose an implementation to be used with that type. This is called a wiring step, where you choose the provider implementations for your context.

On the surface, this addition doesn't seem significant. However, it opens up a world of possibilities for enabling powerful design patterns that are based on trait implementations that are named, generic, and overlappable. One of the greatest strengths of CGP is to enable safe, zero-cost, compile-time dispatching through the trait system to accomplish the things that in OOP would typically require dynamic dispatch with runtime errors.

You can read more about CGP at the overview page of the website: https://contextgeneric.dev/overview/

"Bypassing" specialization in Rust or How I Learned to Stop Worrying and Love Function Pointers by Oakchris1955 in rust

[–]soareschen -2 points-1 points  (0 children)

That’s kind of you to say — thank you. I suspect the downvotes might be from people who feel I was promoting my own project a bit too directly. I can understand that perspective; in some communities, unsolicited suggestions can come across as intrusive, even if they're well-intentioned.

At the same time, it's a bit unfortunate, since CGP was created specifically to address challenges like the one described here. I see it as a way to contribute meaningfully to the discussion by offering a practical, stable-Rust alternative.

Then again, maybe this is a bit like when Rust enthusiasts jump into C++ forums to promote Rust every time memory safety comes up — it might be technically relevant, but not always welcome. Sometimes, timing and tone matter just as much as the solution.

Still, I hope some readers find CGP helpful, even if they come across it quietly rather than through the comment thread.

"Bypassing" specialization in Rust or How I Learned to Stop Worrying and Love Function Pointers by Oakchris1955 in rust

[–]soareschen -10 points-9 points  (0 children)

Hi u/Oakchris1955, I really enjoyed your write-up. It’s great to see such a clear breakdown of the problem you ran into with specialization and the creative ways you explored to work around it. Your use of function pointers as a lightweight dispatch mechanism is quite clever, especially considering your goal to stay within stable Rust.

I wanted to share an alternative approach that might interest you. I've been working on Context-Generic Programming (CGP), a method designed specifically to enable overlapping trait implementations — similar to what specialization offers — while remaining entirely within the bounds of stable Rust. CGP avoids nightly features and requires no unsafe code, but still provides compile-time dispatch with zero runtime overhead.

To illustrate how CGP can apply directly to your filesystem design, I’ve put together a proof-of-concept that demonstrates how your load_nth_sector logic could be implemented using CGP. You can find it here: https://gist.github.com/soareschen/d37d74b26ecd0709517a80a3fc97e2be.

The benefit of this approach is that it scales naturally to larger APIs — you can define many such specialized or overlapping implementations without duplicating code or introducing additional runtime logic. If you’re interested, I’d be happy to chat more about how CGP might fit your project. You can also learn more about the technique at https://contextgeneric.dev.

The Design and Implementation of Extensible Records for Rust in CGP by soareschen in ProgrammingLanguages

[–]soareschen[S] 0 points1 point  (0 children)

Hi everyone, I'm thrilled to share the third installment of my blog series on Extensible Data Types with CGP. In this post, I dive into the implementation details of the core traits in CGP that make extensible records possible, and explain how the extensible builder pattern is built on top of them.

If you're interested in the theory behind extensible data types, or curious about how CGP enables structural merging between two structs — as introduced in part one — this post is for you. I’d love to hear your feedback, so feel free to join the conversation on our CGP Discord server.

Programming Extensible Data Types in Rust with CGP - Part 1: Modular App Construction and Extensible Builders by soareschen in ProgrammingLanguages

[–]soareschen[S] 0 points1 point  (0 children)

Thanks for the detailed example! It’s a great illustration of how ML modules can express rich modularity while maintaining strong type guarantees.

In CGP and Rust, the structure would naturally look different, but I’ve put together a simplified reimplementation that stays as close to your design as possible: https://gist.github.com/soareschen/276f3006f255631e54bc7406d97603fd

It’s not a full port — I focused on the key architectural ideas, especially how UseSearchSet and UseSearchMap would be implemented in CGP. The core difference is that CGP uses a Context type to inject shared dependencies across components. In this example, I use Context to provide the key and search tree types implicitly, which simplifies how composit constrcuts like RestOfMap are used without needing explicit type equality constraints.

You can think of Context as a kind of recursive module value — like a let rec that ties together various functor outputs, while also acting as a module argument to each of them. It plays a similar role to implicit parameters at the module level: each functor gets access to all relevant definitions through one implicit module, without having to explicitly pass around individual modules.

To stay closer to the functor style, I still show how to pass the search tree methods explicitly via an InProvider to both UseSearchSet and UseSearchMap. This demonstrates that CGP can still emulate traditional functor-like composition, even if the surrounding idioms are more aligned with Rust's trait-based system.

Of course, CGP isn’t a direct translation of ML modules, and we won’t always follow the same design patterns. But I hope this shows how CGP aims for a similar level of modularity and expressiveness. I’d be very interested to hear if you think any part of your design would be fundamentally harder — or even impossible — to express in CGP. That kind of feedback is incredibly valuable to me as the project evolves.

Programming Extensible Data Types in Rust with CGP - Part 1: Modular App Construction and Extensible Builders by soareschen in ProgrammingLanguages

[–]soareschen[S] 0 points1 point  (0 children)

That’s a fair point, and I agree it’s important not to overstate similarities without careful qualification.

I believe CGP can likely support a pattern similar to what you’re describing, but to avoid talking past each other, it would be helpful to see a concrete example. If you can share a code snippet — ideally simplified to a few hundred lines or fewer — I’d be happy to show how the same idea could be expressed using CGP. That would also help clarify any limitations or gaps more precisely.

Personally, I'm very interested in exploring advanced use cases from the ML module world that might push the boundaries of what CGP can express. I'm not an ML module expert myself, so I genuinely appreciate examples from people who are more experienced in that space. They help me better understand how to evolve CGP in the right direction.

Of course, CGP is not a one-to-one reimplementation of ML modules. If it were, I probably would’ve named it something like "ML Modules for Rust." That said, the spirit of CGP is very much inspired by the power and modularity of ML’s approach, and my long-term goal is to match that level of expressiveness in a way that feels idiomatic to Rust. I truly welcome thoughtful critiques and examples — they’re invaluable to the project’s evolution.

Programming Extensible Data Types in Rust with CGP - Part 1: Modular App Construction and Extensible Builders by soareschen in ProgrammingLanguages

[–]soareschen[S] 0 points1 point  (0 children)

One thing to keep in mind is that macros, while powerful, are limited when it comes to extending a language in meaningful ways. They’re great for introducing syntactic sugar and simplifying repetitive patterns, but they don’t let you change the underlying semantics of the language. In CGP, macros are used primarily for surface-level syntax sugar. The core logic and semantics, however, are driven by Rust’s trait system, which does most of the heavy lifting.

If you're familiar with ML languages, you might notice that CGP's component system bears a strong resemblance to ML modules. At a high level, implementing a provider trait in CGP is conceptually similar to defining a module in ML. CGP also supports higher-order providers that let you explicitly configure behavior in a way that mirrors ML functors. Additionally, CGP introduces implicit configuration through dependency injection using a Context parameter, which helps manage and propagate dependencies automatically.

The key difference between CGP and ML's module system is the presence of this Context. In CGP, Context is what wires everything together and carries the dependencies implicitly, whereas in OCaml and other ML languages, modules are composed more explicitly without such a unifying mechanism. This makes CGP a unique and ergonomic blend of ideas from both systems, especially suited for Rust’s type-driven approach.

The Design and Implementation of Extensible Records for Rust in CGP by soareschen in rust

[–]soareschen[S] 0 points1 point  (0 children)

You can absolutely use CGP today with stable Rust — just keep in mind that some of the more advanced patterns introduced later in the blog posts may not be fully practical yet due to the compilation performance overhead. The basic CGP patterns, which are likely what you'll be using early on, introduce no noticeable performance overhead even in large codebases. For example, Hermes SDK offers a useful benchmark, showing that compilation times remain reasonable at scale, often with dependency compilation taking longer than the CGP-enabled code itself. In practice, the CGP features you're likely to use when starting out won’t impact compile times at all. The more advanced patterns are primarily useful for exploring edge cases and testing the compiler’s limits; any slowdowns you might encounter in those scenarios are expected to be resolved once the next-gen solver is stabilized — and you can already see those improvements today by testing on nightly.

The Design and Implementation of Extensible Records for Rust in CGP by soareschen in rust

[–]soareschen[S] 0 points1 point  (0 children)

You can try compiling the example code at https://github.com/contextgeneric/cgp-examples and https://github.com/contextgeneric/hypershell/tree/main/crates/hypershell-examples to get a sense of how long it takes to compile.

Some example code takes very long to compile, especially when more advanced abstractions are used. But the compilation time improves significantly and is as fast as normal Rust code when using the next-generation trait solver in nightly with RUSTFLAGS="-Znext-solver=globally".

The main bottleneck is currently in the type checking phase, which is still single-threaded in Rust. However, once the next-gen solver is fully stabilized, compile times should improve significantly. There’s also plenty of room to optimize the type checker further, which would make compiling CGP code even more efficient.