I think it might be worth mentioning "emulating structural typing" as a key phrase here. AFAICT, the functions are basically asserting info about the fields of Self.
Coining a new phrase "context-generic programming" makes it harder to understand what you mean (I think this was the reaction in r/rust).
Hi @typesanitizer, thanks for your comment! You are right that this is much more similar to the concepts of structural typing, row polymorphism, or extensible records, which CGP has already covered in the past.
In fact, CGP has long supported structural typing capabilities through the #[cgp_auto_getter] macro, which requires fields to be defined and accessed through a separate getter trait, as shown below:
The game-changing improvement in v0.7.0 is therefore less about introducing structural typing itself, and more about a language design decision to present the syntax of structural typing in the form of implicit arguments, and blanket implementations in the form of plain functions. This is a significant improvement in terms of developer experience, because developers no longer need to understand the concept of structural typing, or learn about getter traits and blanket implementations just to take advantage of it.
While #[cgp_fn] itself does resemble structural typing, the remaining parts of CGP are less about structural typing and more about writing code that is generic over any Context type that provides capabilities beyond just field accessors, hence the name "context-generic". If you would like to learn more about what other features CGP provides, you can skip to the second tutorial on static dispatch to learn about how CGP bypasses the coherence restriction and enables overlapping implementations to be defined and dispatched.
This is a significant improvement in terms of developer experience, because developers no longer need to understand the concept of structural typing, or learn about getter traits and blanket implementations just to take advantage of it.
When I'm using this library and I hit a compilation error. I'm reasonably sure I'm going to have to understand what the desugaring is, at least at a high-level. Moreover, in the case of the web developers, many of them are already familiar with structural typing thanks to TypeScript.
So I think you're overstating the case a bit on how hiding the structural typing going on is actually a good thing.
More generally, I think your messaging could benefit from being more concise. My point was "emulating structural typing" is a short phrase which perhaps helps capture the message more clearly than what you've written in your post.
I found the explanation and the syntax of it confusing. The stated goal of not passing extra arguments sounded like thread-local-storage hacks emulating Scala's implicit, but they're not doing that.
The example looks like a classic OOP shape area, which seemed like they've invented data inheritance, but using even less OOP syntax than Rust's not-really-OOP.
but as far as I understand (and I could be wrong for the third time…) this is just reading struct fields by name, skipping nominal typing of the structs themselves.
Hi @kornel, thanks for your comment! You are right that this is much more similar to the concepts of structural typing, row polymorphism, or extensible records which CGP has already covered in the past. In fact, CGP has long had support for structural typing through the #[cgp_auto_getter] macro, which requires fields to be defined and accessed through a separate getter trait like:
The game-changing improvement in v0.7.0 is therefore less about introducing structural typing itself, and more about a language design decision to present the syntax of structural typing in the form of implicit arguments, and blanket implementations in the form of plain functions. This is a significant improvement in terms of developer experience, because developers no longer need to understand the concept of structural typing, or learn about getter traits and blanket implementations just to take advantage of it.
You are also not mistaken that the syntax and examples of CGP are deliberately chosen to be familiar to developers coming from an OOP background. The majority of Rust programmers come from OOP languages like Java, and many of them lack familiarity with functional programming concepts like generics and traits. Ironically, a key aspect of CGP's language design is to hide as much of the "generic" Context part as possible and instead present it through Self to make it look similar to OOP classes. This is as much of a necessary evil as how Rust initially designed its syntax to look closer to OOP languages than to more FP-like syntax. And in both cases, I think the choice has been really successful in gaining acceptance from OOP developers, which is why I'd argue that syntax genuinely does matter in the adoption of programming languages.
Three annotations do the work here. #[cgp_fn] augments the plain function and turns it into a context-generic capability. &self provides a reference to whatever context this function is called on. And #[implicit] on both width and height tells CGP to fetch those values automatically from &self instead of requiring the caller to supply them.
Apparently you don't mark compatibility with that on the object/class side, unlike normal Rust polymorphism, so maybe duck typing for statically typed code?
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. 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.
Hi @Student, You are also not mistaken that the syntax and examples of CGP are deliberately chosen to be familiar to developers coming from an OOP background. The &self in the function signature is really a generic context: &Context type, but the macro hides the use of generics and presents the generic Context as Self.
The majority of Rust programmers come from OOP languages like Java, and many of them lack familiarity with functional programming concepts like generics and traits. Ironically, a key aspect of CGP's language design is to hide as much of the "generic" Context part as possible and instead present it through Self to make it look similar to OOP classes. This is as much of a necessary evil as how Rust initially designed its syntax to look closer to OOP languages than to more FP-like syntax. And in both cases, I think the choice has been really successful in gaining acceptance from OOP developers, which is why I'd argue that syntax genuinely does matter in the adoption of programming languages.
typesanitizer | 7 hours ago
I think it might be worth mentioning "emulating structural typing" as a key phrase here. AFAICT, the functions are basically asserting info about the fields of Self.
Coining a new phrase "context-generic programming" makes it harder to understand what you mean (I think this was the reaction in r/rust).
k749gtnc9l3w | 5 hours ago
… while there is also «context-oriented programming», to which this one is less similar than to duck typing / structural typing.
[OP] soareschen | 2 hours ago
Hi @typesanitizer, thanks for your comment! You are right that this is much more similar to the concepts of structural typing, row polymorphism, or extensible records, which CGP has already covered in the past.
In fact, CGP has long supported structural typing capabilities through the
#[cgp_auto_getter]macro, which requires fields to be defined and accessed through a separate getter trait, as shown below:The game-changing improvement in v0.7.0 is therefore less about introducing structural typing itself, and more about a language design decision to present the syntax of structural typing in the form of implicit arguments, and blanket implementations in the form of plain functions. This is a significant improvement in terms of developer experience, because developers no longer need to understand the concept of structural typing, or learn about getter traits and blanket implementations just to take advantage of it.
While
#[cgp_fn]itself does resemble structural typing, the remaining parts of CGP are less about structural typing and more about writing code that is generic over anyContexttype that provides capabilities beyond just field accessors, hence the name "context-generic". If you would like to learn more about what other features CGP provides, you can skip to the second tutorial on static dispatch to learn about how CGP bypasses the coherence restriction and enables overlapping implementations to be defined and dispatched.typesanitizer | 29 minutes ago
When I'm using this library and I hit a compilation error. I'm reasonably sure I'm going to have to understand what the desugaring is, at least at a high-level. Moreover, in the case of the web developers, many of them are already familiar with structural typing thanks to TypeScript.
So I think you're overstating the case a bit on how hiding the structural typing going on is actually a good thing.
More generally, I think your messaging could benefit from being more concise. My point was "emulating structural typing" is a short phrase which perhaps helps capture the message more clearly than what you've written in your post.
kornel | 5 hours ago
This is structural typing.
I found the explanation and the syntax of it confusing. The stated goal of not passing extra arguments sounded like thread-local-storage hacks emulating Scala's implicit, but they're not doing that.
The example looks like a classic OOP shape area, which seemed like they've invented data inheritance, but using even less OOP syntax than Rust's not-really-OOP.
but as far as I understand (and I could be wrong for the third time…) this is just reading struct fields by name, skipping nominal typing of the structs themselves.
[OP] soareschen | 2 hours ago
Hi @kornel, thanks for your comment! You are right that this is much more similar to the concepts of structural typing, row polymorphism, or extensible records which CGP has already covered in the past. In fact, CGP has long had support for structural typing through the
#[cgp_auto_getter]macro, which requires fields to be defined and accessed through a separate getter trait like:The game-changing improvement in v0.7.0 is therefore less about introducing structural typing itself, and more about a language design decision to present the syntax of structural typing in the form of implicit arguments, and blanket implementations in the form of plain functions. This is a significant improvement in terms of developer experience, because developers no longer need to understand the concept of structural typing, or learn about getter traits and blanket implementations just to take advantage of it.
You are also not mistaken that the syntax and examples of CGP are deliberately chosen to be familiar to developers coming from an OOP background. The majority of Rust programmers come from OOP languages like Java, and many of them lack familiarity with functional programming concepts like generics and traits. Ironically, a key aspect of CGP's language design is to hide as much of the "generic"
Contextpart as possible and instead present it throughSelfto make it look similar to OOP classes. This is as much of a necessary evil as how Rust initially designed its syntax to look closer to OOP languages than to more FP-like syntax. And in both cases, I think the choice has been really successful in gaining acceptance from OOP developers, which is why I'd argue that syntax genuinely does matter in the adoption of programming languages.apromixately | 4 hours ago
Do you know any scientific opinions on this? I quite like the idea but I also like checked type aliases which are kind of the opposite.
kornel | 2 hours ago
Graydon wanted Rust to have structural types.
Student | 8 hours ago
Is this…object oriented programming?
k749gtnc9l3w | 7 hours ago
Apparently you don't mark compatibility with that on the object/class side, unlike normal Rust polymorphism, so maybe duck typing for statically typed code?
[OP] soareschen | 2 hours ago
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. 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.
[OP] soareschen | 2 hours ago
Hi @Student, You are also not mistaken that the syntax and examples of CGP are deliberately chosen to be familiar to developers coming from an OOP background. The
&selfin the function signature is really a genericcontext: &Contexttype, but the macro hides the use of generics and presents the genericContextasSelf.The majority of Rust programmers come from OOP languages like Java, and many of them lack familiarity with functional programming concepts like generics and traits. Ironically, a key aspect of CGP's language design is to hide as much of the "generic" Context part as possible and instead present it through Self to make it look similar to OOP classes. This is as much of a necessary evil as how Rust initially designed its syntax to look closer to OOP languages than to more FP-like syntax. And in both cases, I think the choice has been really successful in gaining acceptance from OOP developers, which is why I'd argue that syntax genuinely does matter in the adoption of programming languages.