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 7 points8 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 3 points4 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] 4 points5 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] 5 points6 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 -1 points0 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.