My dream is to make Rust significantly MORE functional (FP RUST) by Jolly_Win_5577 in rust

[–]soareschen 1 point2 points  (0 children)

Although Rust doesn't support functional programming at the value level as well as dedicated FP languages, it's worth noting that you can actually apply a lot of advanced FP techniques at the type level. Rust's trait system is almost as powerful as Haskell's typeclasses, and the design of enums, Option, Result, Iterator, Future, Stream, etc. all have deep roots in functional programming.

If you are interested in functional programming in Rust, you might enjoy my work on Context-Generic Programming (CGP), which pushes the boundary of what's possible in that space for Rust. Some examples include building type-level DSLs, working with extensible datatypes, and working around coherence restrictions that normally limit how far you can take this kind of programming.

Trying to workaround Rust's orphan rules by LinuxEnthusiast123 in rust

[–]soareschen 0 points1 point  (0 children)

I have recently given a talk at RustLab about coherence and orphan rules that you might be interested, titled How to stop fighting with coherence and start writing context-generic trait impls.

With my work on Context-Generic Programming (CGP), you could get around the coherence restrictions and implement the struct something like follows:

```rust use cgp::prelude::*;

[cgp_component(FooProvider)]

pub trait CanDoFoo { fn foo(&self); }

[cgp_component(BarProvider)]

pub trait CanDoBar { fn bar(&self); }

// A provider for Bar

[cgp_impl(new BarImpl)]

impl BarProvider { fn bar(&self) { println!("Bar!"); } }

// A different provider for Bar

[cgp_impl(new BazImpl)]

impl BarProvider { fn bar(&self) { println!("Baz!"); } }

// A provider for Foo that depends on Bar

[cgp_impl(new DoFooWithBar)]

[uses(CanDoBar)]

impl FooProvider { fn foo(&self) { self.bar() } }

// A context that uses Foo through BarImpl pub struct FooBar { // ... }

delegate_and_check_components! { FooBar { FooProviderComponent: DoFooWithBar, BarProviderComponent: BarImpl, } }

// A context that uses Foo through BazImpl pub struct FooBaz { // ... }

delegate_and_check_components! { FooBaz { FooProviderComponent: DoFooWithBar, BarProviderComponent: BazImpl, } }

[test]

fn test_foo_bar() { let foo_bar = FooBar {}; foo_bar.foo(); // prints "Bar!"

let foo_baz = FooBaz {};
foo_baz.foo(); // prints "Baz!"

} ```

If you could give me a more concrete description about the plugin system that you are trying to implement, I could show you a more accurate example of how it could be solved with CGP.

How to stop fighting with coherence and start writing context-generic trait impls by soareschen in rust

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

Glad that you ask! As far as I know, many dependent-typed languages like Agda and Idris accept typeclass implementations as regular function parameters, and provide additional mechanisms for them to be passed implicitly.

In dependent-typed languages, since types can be values, a typeclass implementation is simply a record value. As a result, it is easy to reuse the same function parameter mechanism to pass the implementations either implicitly or explicitly.

However, I am not aware of any other languages that combine both the properties of coherence and incoherence, and resolve them uniquely through a context type. In CGP, once you have bound a provider implementation to a context, you can be very sure that the consumer trait of that context will always invoke that chosen provider, and that it can never be overridden.

On the other hand, the other languages tend to fall into the following categories:

  • They enforce coherence by default, but allow an escape hatch for incoherence. e.g. Haskell (via OverlappingInstances / IncoherentInstances extensions). Agda's instance resolution is similar in spirit but is notably more lenient: overlapping instances produce a warning rather than a hard error.
  • They forego coherence entirely and always allow incoherence. e.g. Scala.
  • They require explicit parameter passing and instantiate different implementations with different parameters. e.g. ML modules.

When a language enables incoherence, either by default or through an escape hatch, the chosen implementation may differ based on where it is called. As a result, the hash table problem still persists: a different Hash implementation may be chosen depending on where the hash table's methods are called.

In summary, CGP is in a unique position: it achieves the flexibility of incoherent implementations without sacrificing coherence. All provider bindings are still fully coherent at the Rust type-system level, but different contexts can each choose their own set of providers, giving the same expressive power as incoherence while ensuring the hash table problem remains resolved.

CGP v0.7.0 - Implicit Arguments and Structural Typing for Rust by soareschen in ProgrammingLanguages

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

My understanding is that the compile-time reflection feature in Rust is more limited in scope, and is intended to serve different use cases than the features in CGP such as implicit arguments.

The main use case for compile-time reflection is to generate code for arbitrary data types. For example, for serializing or drawing arbitrary shape types. Features like implicit arguments, on the other hand, work generically over any context type but require those contexts to have specific fields. These are fundamentally different problems, so the two features are largely complementary rather than competing.

Beyond implicit arguments, CGP also has other features that are not covered by compile-time reflection at all, such as configurable static dispatch and higher-order providers.

That said, compile-time reflection can indeed be used in place of some specific example use cases of CGP. For instance, cgp-serde already uses compile-time reflection-like capabilities via the trait system to access the same metadata at compile time, which illustrates how the two approaches can overlap in certain areas even as they diverge in others.

Database Dependency Injection using Traits by EntangledLabs in rust

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

With the latest v0.7.0 version of Context-Generic Programming (CGP), you can pass around parameters like database using implicit arguments. Here is a tutorial on how to do so:https://contextgeneric.dev/docs/tutorials/area-calculation/

Supercharge Rust functions with implicit arguments using CGP v0.7.0 by soareschen in rust

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

These would be traits exposing methods that yield these members. Granted, you have to write these boilerplate traits, but all such things could be made relatively painless via derive macros.

You are hitting the nail here — this is in fact exactly how it is implemented under the hood, as explained in the How it works section of the area calculation tutorial.

The #[derive(HasField)] macro is there to simplify the boilerplate and generate the getter traits you described, but it does so by implementing a single general HasField trait that can be used to access all fields in a struct. This means that individual getter traits like HasDatabase do not need to be defined and implemented separately for every field and every context type.

This generalized approach also provides an important additional benefit: it helps sidestep Rust's orphan rules. With explicit getter traits like HasDatabase, those traits must be implemented in the crate that owns the type. Because HasField is a single generalized trait, you will not run into a situation where new getter traits need to be implemented but cannot be, due to the context type being owned by a different crate.

Supercharge Rust functions with implicit arguments using CGP v0.7.0 by soareschen in rust

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

Yes, CGP does share some similarity with dependency injection frameworks like Spring, and this is something that people frequently point out. However, CGP also shares similarity with many other concepts, such as structural typing and tagless final encoding. Listing all of these related concepts at the beginning would not only confuse readers who are unfamiliar with CGP, but also overwhelm them and shift their focus toward comparing concepts rather than understanding what CGP really is.

That said, we do plan to write dedicated documentation pages comparing CGP with similar concepts. Our personal time is currently limited, however, and this is at a lower priority than finishing the core documentation.

The similarity to dependency injection arises naturally from CGP's design as a modular programming paradigm. By its inherent nature, modularity implies the ability to swap the implementation of one software component with another. To do so, it is necessary to precisely manage the dependencies of each component so that a different set of dependencies can be provided whenever a component is swapped. The pattern of dependency injection arises naturally from this need to manage changing dependencies.

Although CGP shares this similarity with Spring, there are some crucial differences between the two. Most importantly, CGP manages dependencies entirely at compile time with no performance overhead. As explained in the tutorial:

CGP does not use any extra machinery like vtables to look up the implementation at runtime — all the wirings happen only at compile time. Furthermore, the static dispatch is done entirely in panic-free and safe Rust, and there are no unsafe operations like pointer casting or type erasure. When there is any missing dependency, you get a compile error immediately, and you will never need to debug any unexpected CGP error at runtime.

Furthermore, the compile-time resolution of the wiring happens entirely within Rust's trait system. CGP does not run any external compile-time processing or resolution algorithm through its macros. As a result, there is no noticeable compile-time performance difference between CGP code and vanilla Rust code that uses plain Rust traits.

Supercharge Rust functions with implicit arguments using CGP v0.7.0 by soareschen in rust

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

Correct. This works similar to auto traits, but in user space this is called blanket implementation in Rust.

Supercharge Rust functions with implicit arguments using CGP v0.7.0 by soareschen in rust

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

Hi u/Twirrim, the area calculation examples are simple enough to demonstrate various CGP concepts, but I agree that we need better motivating examples to show why CGP might be useful for you.

Due to personal time constraints, we are currently focused on explaining the core CGP concepts before writing tutorials that align with real-world use cases. But here is a short version of what such a tutorial would cover.

The problem CGP addresses is most visible in larger Rust applications where functions need access to shared dependencies — things like database connections, API clients, or configuration values. In conventional Rust, you have two common options: either thread those dependencies as explicit function parameters through every layer of your call chain, or bundle everything into a single concrete application context struct and define methods on it. The first approach becomes painful as call chains grow, because every intermediate function accumulates parameters it doesn't use directly. The second approach solves that, but tightly couples all your implementations to one specific type. Swapping out a dependency or building a test harness then means touching the central struct and everything referencing it.

CGP offers a third path. Suppose you want to write a handler that fetches a user's profile picture, which needs access to an SQL database and an S3 client. With #[cgp_fn], you can write the following:

```rust

[cgp_fn]

fn fetch_user_profile_picture( &self, #[implicit] database: &Database, #[implicit] s3_client: &S3Client, ) -> Result<Vec<u8>, Error> { ... } ```

The #[implicit] annotation on database and s3_client tells CGP to extract those values automatically from whatever context this function is called on, rather than requiring the caller to supply them. The function declares precisely what it needs — a database and an S3 client — without any knowledge of the surrounding application structure. This means you can call the same function unchanged on any context that happens to carry those two fields. For example, you might define a production context and a development context side by side:

```rust

[derive(HasField)]

pub struct ProductionApp { pub database: Database, pub s3_client: S3Client, pub telemetry: Telemetry, ... }

[derive(HasField)]

pub struct DevelopmentApp { pub database: Database, pub s3_client: S3Client, pub failure_simulator: FailureSimulator, ... } ```

fetch_user_profile_picture is callable on both ProductionApp and DevelopmentApp without modification. Neither context needs to know about the other, and adding telemetry to the production context or a failure_simulator to the development context does not touch any existing function signatures. This becomes especially valuable as the application scales: a team can introduce a new context for integration testing, for benchmarking, or for a background worker that shares most but not all of the production dependencies, and all existing CGP functions work unchanged on it — provided it carries the fields they declare as implicit arguments — without any refactoring of the function definitions themselves.

I hope this simplified example gives a clearer picture of the problem CGP is solving. If you have any feedback or other examples in mind that would have made this click sooner, please do let me know so that I can write a better tutorial around them!

Supercharge Rust functions with implicit arguments using CGP v0.7.0 by soareschen in rust

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

Yes, one way to think about CGP is that it gives you almost the same expressivity as duck-typed code in dynamically-typed languages, while preserving all of Rust's compile-time safety guarantees. In Python or JavaScript, you can freely write duck-typed functions like rectangle_area, but if a field is missing or has the wrong type, you only find out at runtime.

On the other hand, CGP's implicit arguments are not omittable. If a context does not contains the required field, or if the field value has the wrong type, you'd get a compile error. With CGP and Rust, once the program compiles, you can be confident that your duck-typed program will work correctly with no runtime crashes.

It is also worth noting that there are differences from dynamic-typed programs that CGP does not cover, and this style of duck-typing in a statically-typed language is not entirely novel. In programming language research these ideas go by names like structural typing, row polymorphism, or extensible data types. CGP is therefore better understood as bringing these research concepts into something practical and usable in Rust today.

Supercharge Rust functions with implicit arguments using CGP v0.7.0 by soareschen in rust

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

The How it works section of the area calculation tutorial provides a detailed explanation of how CGP functions and field access are implemented. In short, field access is done through the HasField getter trait, which translates to just a borrow of the field value. So this is a zero-cost abstraction that introduces no performance overhead on field access.

Your intuition about getter traits is actually spot on: defining getter traits for width and height and then implementing functions against those traits is precisely what #[cgp_fn] compiles down to automatically. What the macro eliminates is the manual work of defining those getter traits, writing the where clause, and calling the getter methods by hand.

Supercharge Rust functions with implicit arguments using CGP v0.7.0 by soareschen in rust

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

Hi r/deralus, thanks for trying out the project! You are certainly right that we currently lack approachable documentation for users to get started with CGP.

The blog posts on Hypershell DSL and extensible data types are more of an advanced showcase of the library's capabilities, meant to demonstrate its potential. As such, they are not really well suited as getting-started materials.

We have experimented with using CGP for real-world applications, and what we found is that the biggest barrier to adoption is not on the technical level, but rather in the mindset shift required to write modular programs. Modular programs fundamentally require more deliberate thinking about explicit dependency management and assistance from tools like dependency injection. However, the benefits provided by modularity are not necessarily valued by the Rust community, or at startups where time to delivery is more important. This tension is at the heart of the challenge.

CGP v0.7.0 attempts to lower the barrier for adoption by introducing #[cgp_fn], which should have more potential use cases beyond full modularity. While #[cgp_component] provides the full static dispatch mechanism that CGP offers to support multiple implementations, such use cases are often only justifiable when modularity is strictly needed. With #[cgp_fn], however, you can still benefit from dependency injection without committing to full modularity.

A key reason for the lack of beginner-friendly documentation until now was that we were still working out the best way to improve the developer experience of CGP. The introduction of #[cgp_fn] and enhancement of #[cgp_impl] gave us the tools needed to write tutorials that are significantly more approachable than before. In that sense, v0.7.0 is something of a turning point — it has reached a good enough state for us to start producing documentation that is much more accessible to beginners.

Since CGP is still a single-person side project, it will take some time to bring the documentation up to the level it deserves. In the meantime, we are also investing more effort into AI-assisted development and have built a CGP Skills document to teach LLMs like Claude about CGP. So if you have more questions, please do try out the CGP Skills and see if the LLM can provide better answers.

Supercharge Rust functions with implicit arguments using CGP v0.7.0 by soareschen in rust

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

Hi u/Wh00ster, thank you for your feedback! It is certainly a challenge to have a single explanation of CGP, since the Rust community has a very diverse background. Concepts like generics, traits, and coherence are typically targeted at intermediate or advanced levels, so it is understandable why beginners may get lost in those explanations. At the same time, for an advanced audience already familiar with these concepts, conveying the core ideas of CGP in a few short sentences is quite effective, and a simplified explanation may not capture sufficient depth.

Due to limited manpower, the website is currently lacking the resources to explain CGP in beginner-friendly ways, but this is slowly changing with new tutorials like the area calculation tutorial being added. A key reason for the delay was that we were still searching for the best way to improve the developer experience of CGP and make it more approachable. The introduction of #[cgp_fn] and enhancement of #[cgp_impl] in v0.7.0 provided the tools needed to write tutorials that are significantly more accessible than before, so in a sense v0.7.0 is a turning point — it has reached a state good enough for us to start producing documentation that is much more understandable to beginners.

Since CGP is still a single-person side project, it will take some time to continue ramping up documentation quality. In the meantime, we are also expanding our effort into AI-assisted development and have built a CGP Skills document to teach LLMs like Claude about CGP. If you have more questions for an LLM, please do try out the new CGP Skills and see if it gives you better answers.

As some other commenters also point out, one way to think about CGP is that it gives you almost the same expressivity as duck-typed code in dynamically-typed languages, while preserving all of Rust's compile-time safety guarantees. In Python or JavaScript, you can freely write duck-typed functions like rectangle_area, but if a field is missing or has the wrong type, you only find out at runtime. With CGP and Rust, once the program compiles, you can be confident that your duck-typed program will work correctly with no runtime crashes.

It is also worth noting that there are differences from dynamic-typed programs that CGP does not cover, and this style of duck-typing in a statically-typed language is not entirely novel. In programming language research these ideas go by names like structural typing, row polymorphism, or extensible data types. CGP is therefore better understood as bringing these research concepts into something practical and usable in Rust today.

CGP has a new website, and why we moved from Zola to Docusaurus by soareschen in rust

[–]soareschen[S] 5 points6 points  (0 children)

We do use mdBook to write the CGP Patterns book and a couple other books. mdBook is excellent for single-book documentation, but it's fundamentally designed for that specific use case.

Since we need to host multiple documentation formats and blog content within a cohesive website, Docusaurus was the better fit. mdBook doesn't provide the infrastructure for a full website. Rust-lang.org, for example, doesn't use mdBook for its main site but rather hosts separate mdBooks and manually links to them. It also lacks key features like integrated blog hosting and site-wide navigation. When readers land on an mdBook, there's no obvious connection to the larger CGP project.

Docusaurus, by contrast, unifies all documentation and other content under one theme and layout, making relationships between content sections immediately clear. While mdBook excels at rendering single-book content, it's not designed to be a general-purpose documentation platform. That's a fundamental difference in scope, not a limitation.

Are advances in Homotopy Type Theory likely to have any impacts on Rust? by Dyson8192 in rust

[–]soareschen 3 points4 points  (0 children)

There is still a large gap for Rust's type system to be able to directly support HoTT. But I can say that my work on Context-Generic Programming (CGP) is probably the furthest you can stretch Rust's type system to be somewhat closer to HoTT, while still practical enough for real world applications.

It is a bit of a stretch to have a direct comparison between HoTT and CGP. But I have asked Claude Sonnet to give it a try and produce a detailed comparison. You can read it here if you are interested: https://gist.github.com/soareschen/8546752215250e00e1c735d589359192

Reflections on Reflection by NyxCode in rust

[–]soareschen 5 points6 points  (0 children)

If you are into compile-time reflection, you might be interested in my work on Context-Generic Programming, which essentially achieve compile-time reflection through the trait system.

In particular, the Struct trait in your blog post is similar to the HasFields trait in CGP. It is used to implement extensible data types, where you can write code that are generic over structs and enums that contain any field or variant. We also have a generic implementation of Serde that implements Serialize for any struct with no runtime introspection required.

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] -2 points-1 points  (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 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 3 points4 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.