Skip to content

Commit c31c8b8

Browse files
committed
Finish updating debugging chapter
1 parent 41be3d1 commit c31c8b8

File tree

1 file changed

+213
-2
lines changed

1 file changed

+213
-2
lines changed

content/debugging-support.md

Lines changed: 213 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,21 @@ directly requires `FormatAsJsonString` to also implement `IsProviderFor` with th
483483
By doing so, we resurface the constraints to Rust, so that it would recursively look into the
484484
`IsProviderFor` trait bounds, and print out any unsatisfied constraints in the error messages.
485485

486+
At this point, you may wonder why not link the provider trait directly within the delegated implementation of `IsProviderFor`, such as:
487+
488+
```rust,ignore
489+
impl<Context> IsProviderFor<StringFormatterComponent, Context>
490+
for PersonComponents
491+
where
492+
FormatAsJsonString: StringFormatter<Context>,
493+
{
494+
}
495+
```
496+
497+
The main reason to _not_ do this is that it requires direct access to the provider trait, which is not as simple dealing with simple types. Furthermore, the provider trait may contain additional where clauses, which would also need to be propagated explicitly.
498+
499+
By making use of `IsProviderFor`, we are essentially "erasing" everything at the trait level, and use a single trait to represent all other provider traits. After all, the only thing that we are interested here is to propagate the constraints for the purpose of showing better error messages.
500+
486501
## Check Traits
487502

488503
Now that we have the wirings for `IsProviderFor` in place, we can implement _check traits_ to check
@@ -553,8 +568,6 @@ note: required by a bound in `CanUsePersonComponents`
553568
```
554569

555570
As we can see, the use of `IsProviderFor` helps make it possible for us to debug our CGP programs again.
556-
This significantly improves the developer experience for CGP, which was significantly harder to debug
557-
prior to the introduction of this technique in v0.4.0.
558571

559572
## `CanUseComponent` Trait
560573

@@ -614,3 +627,201 @@ error[E0277]: the trait bound `Person: CanUseComponent<StringFormatterComponent>
614627
| ^^^^^^ the trait `Serialize` is not implemented for `Person`
615628
...
616629
```
630+
631+
## Limitations
632+
633+
The use of `IsProviderFor` significantly improves the developer experience for CGP, which was significantly harder
634+
to debug prior to the introduction of this technique in v0.4.0.
635+
636+
At this point, you might be concerned of the additional boilerplate required to implement and propagate the constraints for `IsProviderFor`. However, as we will see in the [next chapter](./component-macros.md), the CGP macros will automate the bulk of the implementation of `IsProviderFor`, and only require lightweight attribute macros to be applied to enable the code generation.
637+
638+
It is worth noting that the `IsProviderFor` trait is introduced as a workaround to improve the error messages of CGP code in Rust. Hypothetically, if Rust could provide better support of showing the relevant error messages, we could entirely remove the use of `IsProviderFor` in future versions of CGP.
639+
640+
On the other hand, the use of `IsProviderFor` can serve as a great showcase to the Rust compiler team on what error messages should have been shown by default, and make it easier to evaluate what should be the official fix in the Rust compiler.
641+
642+
That said, the error messages shown via `IsProviderFor` can sometimes be too verbose, especially when an application contains many providers with deeply nested dependencies. The reason is that any unsatisfied constraint from deeply nested dependencies can propagate all the way up through chains of `IsProviderFor` implementations. Although this can help us pinpoint the root cause, it could generate too much noise when showing all errors from the intermediary layers.
643+
644+
When encountering heap of error messages generated from `IsProviderFor`, a useful tip is that the relevant error message may be hiding near the bottom. So it may be useful to read from the bottom up instead of top down.
645+
646+
## Interactive Debugging with Argus
647+
648+
A potential improvement that we are currently exploring is to make use of [Argus](https://cel.cs.brown.edu/paper/an-interactive-debugger-for-rust-trait-errors/) to help navigate error messages generated from CGP code. Argus provides an interactive debugger for trait-related errors, which may be well suited to be used for debugging CGP code.
649+
650+
We will add further details in the future to share potential integration with Argus. For now, interested readers are encouraged to check out the project, and perhaps make contribution to make such integration possible.
651+
652+
## Conclusion
653+
654+
In this chapter, we have learned how CGP makes use of the `IsProviderFor` trait to help show relevant error messages when encountering unsatisfied constraints. In the next chapter, we will walk through how to automate the generation of all the relevant boilerplates that we have learned so far, and write succint CGP code using macros.
655+
656+
We will show the full example that we have walked through earlier, with the addition of `IsProviderFor` into the code:
657+
658+
```rust,compile_fail
659+
# extern crate anyhow;
660+
# extern crate serde;
661+
# extern crate serde_json;
662+
#
663+
use anyhow::Error;
664+
use serde::{Deserialize, Serialize};
665+
666+
pub trait HasProvider {
667+
type Provider;
668+
}
669+
670+
pub trait IsProviderFor<Component, Context, Params = ()> {}
671+
672+
pub trait DelegateComponent<Name> {
673+
type Delegate;
674+
}
675+
676+
pub trait CanUseComponent<Component, Params = ()> {}
677+
678+
impl<Context, Component, Params> CanUseComponent<Component, Params> for Context
679+
where
680+
Context: HasProvider,
681+
Context::Provider: IsProviderFor<Component, Context, Params>,
682+
{
683+
}
684+
685+
pub struct StringFormatterComponent;
686+
687+
pub struct StringParserComponent;
688+
689+
pub trait CanFormatToString {
690+
fn format_to_string(&self) -> Result<String, Error>;
691+
}
692+
693+
pub trait CanParseFromString: Sized {
694+
fn parse_from_string(raw: &str) -> Result<Self, Error>;
695+
}
696+
697+
pub trait StringFormatter<Context>:
698+
IsProviderFor<StringFormatterComponent, Context>
699+
{
700+
fn format_to_string(context: &Context) -> Result<String, Error>;
701+
}
702+
703+
pub trait StringParser<Context>:
704+
IsProviderFor<StringParserComponent, Context>
705+
{
706+
fn parse_from_string(raw: &str) -> Result<Context, Error>;
707+
}
708+
709+
impl<Context> CanFormatToString for Context
710+
where
711+
Context: HasProvider,
712+
Context::Provider: StringFormatter<Context>,
713+
{
714+
fn format_to_string(&self) -> Result<String, Error> {
715+
Context::Provider::format_to_string(self)
716+
}
717+
}
718+
719+
impl<Context> CanParseFromString for Context
720+
where
721+
Context: HasProvider,
722+
Context::Provider: StringParser<Context>,
723+
{
724+
fn parse_from_string(raw: &str) -> Result<Context, Error> {
725+
Context::Provider::parse_from_string(raw)
726+
}
727+
}
728+
729+
impl<Context, Component> StringFormatter<Context> for Component
730+
where
731+
Component: DelegateComponent<StringFormatterComponent>
732+
+ IsProviderFor<StringFormatterComponent, Context>,
733+
Component::Delegate: StringFormatter<Context>,
734+
{
735+
fn format_to_string(context: &Context) -> Result<String, Error> {
736+
Component::Delegate::format_to_string(context)
737+
}
738+
}
739+
740+
impl<Context, Component> StringParser<Context> for Component
741+
where
742+
Component: DelegateComponent<StringParserComponent>
743+
+ IsProviderFor<StringParserComponent, Context>,
744+
Component::Delegate: StringParser<Context>,
745+
{
746+
fn parse_from_string(raw: &str) -> Result<Context, Error> {
747+
Component::Delegate::parse_from_string(raw)
748+
}
749+
}
750+
751+
pub struct FormatAsJsonString;
752+
753+
impl<Context> StringFormatter<Context> for FormatAsJsonString
754+
where
755+
Context: Serialize,
756+
{
757+
fn format_to_string(context: &Context) -> Result<String, Error> {
758+
Ok(serde_json::to_string(context)?)
759+
}
760+
}
761+
762+
impl<Context> IsProviderFor<StringFormatterComponent, Context>
763+
for FormatAsJsonString
764+
where
765+
Context: Serialize,
766+
{
767+
}
768+
769+
pub struct ParseFromJsonString;
770+
771+
impl<Context> StringParser<Context> for ParseFromJsonString
772+
where
773+
Context: for<'a> Deserialize<'a>,
774+
{
775+
fn parse_from_string(json_str: &str) -> Result<Context, Error> {
776+
Ok(serde_json::from_str(json_str)?)
777+
}
778+
}
779+
780+
impl<Context> IsProviderFor<StringParserComponent, Context>
781+
for ParseFromJsonString
782+
where
783+
Context: for<'a> Deserialize<'a>,
784+
{
785+
}
786+
787+
// Note: We pretend to forgot to derive Serialize here
788+
#[derive(Deserialize, Debug, Eq, PartialEq)]
789+
pub struct Person {
790+
pub first_name: String,
791+
pub last_name: String,
792+
}
793+
794+
pub struct PersonComponents;
795+
796+
impl HasProvider for Person {
797+
type Provider = PersonComponents;
798+
}
799+
800+
impl DelegateComponent<StringFormatterComponent> for PersonComponents {
801+
type Delegate = FormatAsJsonString;
802+
}
803+
804+
impl<Context> IsProviderFor<StringFormatterComponent, Context>
805+
for PersonComponents
806+
where
807+
FormatAsJsonString: IsProviderFor<StringFormatterComponent, Context>,
808+
{
809+
}
810+
811+
impl DelegateComponent<StringParserComponent> for PersonComponents {
812+
type Delegate = ParseFromJsonString;
813+
}
814+
815+
impl<Context> IsProviderFor<StringParserComponent, Context> for PersonComponents where
816+
ParseFromJsonString: IsProviderFor<StringParserComponent, Context>
817+
{
818+
}
819+
820+
pub trait CanUsePerson:
821+
CanUseComponent<StringFormatterComponent>
822+
+ CanUseComponent<StringParserComponent>
823+
{
824+
}
825+
826+
impl CanUsePerson for Person {}
827+
```

0 commit comments

Comments
 (0)