A very low-effort way to learn good Rust patterns is to put
#![warn(clippy::all)]
at the top of your crate’s entrypoint. This enables Rust’s default linter. It’s a lot more friendly and focused on good design than you might expect, often suggesting more elegant alternatives. Plus, many of its suggestions can be applied automatically in an environment like VS Code + rust-analyzer plugin.
Wait what? You can use dyn on the stack (without a Box)?
This has been one of my biggest complaints about Rust: I've been using it for years at this point. I read most of the Book, I've read a few unofficial books. And I do love the language, but it has so many cases like this where things that you're allowed to do (syntactically or otherwise) are somehow so non-obvious that you can miss them entirely. I still get blindsided by something like this every couple months, and I always end up a little mad that I've been doing things the hard way until some obscure unofficial material (or more often, a stack overflow answer) teaches me about an entire feature that I didn't know existed.
Rust is really great at telling you what you can't do, and in many ways its documentation is incredibly thorough, but it has a real problem when it comes to discoverability and establishing a consistent mental-model of what its syntax actually means (and how you can then apply it to other situations). I don't know what the root cause of this problem is. But it's really distressing to me each time I discover a huge blind-spot; it makes me feel like I never fully understood the language concepts that I thought I understood.
I say all of this out of love: I really want Rust to succeed. I still prefer to use it despite this issue. I just believe this is a huge thorn in its side, especially when it comes to adoption, for which it already has an uphill climb.
Honestly, who cares about not knowing that something was possible if you never wanted it? `dyn` working on the stack is more consistent than it not working on the stack.
I'd say one way to a language more completely is to:
a) Read its stackoverflow pages (not sure about Rust but this helped a ton with C++)
b) Get code reviews from others. The rust community is fairly active, or at least it was back when there was just an IRC channel, I don't know as much now since I don't get onto whatever the medium is today. Ask people if there's an easier way to do something.
c) Read others code. I, for example, like to review some of my dependencies just so I understand a bit more about how they work, and I've picked up a lot from that. I used to get on IRC in the morning while I was kinda getting into my work-day and see if I couldn't help others out, this really taught me a lot.
dyn just requires the object to be behind some kind of pointer. The vast majority of the time, that pointer is a Box or an Rc/Arc, but any form of indirection can work.
A list of design patterns for a language amounts to a list of weaknesses in the language or in its library.
If the pattern could be captured in a library, that pattern would just be using the library. If the core language provided the feature, the pattern would just amount to using the feature.
Generally it is better to improve the language to the point where the feature can be added as a library component, but sometimes that is too hard. Thus, for most languages nowadays a "dictionary" type is provided in the core language, because a useful hash table library cannot be written with language primitives. In C, hash tables are open-coded again in each place where one is needed, because the language provides neither the feature, nor facilities sufficient to capture it in a library. Rust is powerful and expressive enough that hash tables are library components.
Conversely, pattern matching and coroutines are built into Rust. It should never be forgotten that (1) this was because the language was not expressive enough to capture the features satisfactorily in a library; and that (2) it would be better if, someday, the core feature became unnecessary because the language became expressive enough provide it as a library.
One reason it is better for features to be provided in a library is that another library can implement a variation on the feature that might not be as widely useful as the core version, but is better tuned to a less-common but still important use.
Another is that users can invent whole new features by combining powerful primitives that the language designers would not have time, or possibly inclination, to implement themselves.
Thus, in a certain sense, all patterns are anti-patterns.
Is there anything that can be achieved by using the visitor pattern [0], that cannot be done by using pattern matching? I have only used the visitor pattern in languages that do not have pattern matching as a language feature (e.g. Java before it got a Scala-like `switch` construct [1]).
Edit: one limitation of pattern matching is, that all values need a common supertype (e.g. be variants of the same enum in Rust, if we see each variant as a type and the enum as the common supertype. There is an RFC [2] to make enum variants accessible as types), while the visitor pattern could be implemented for any set of independent types. On the other hand, you then cannot have a typed collection/container that contains values of these types, so you'd need some common trait like `Visitable` so you could accept an `Vec<dyn Visitable>`.
I find that this kind of pattern can be useful if you have a default implementation for each visit_foo method.
For example, suppose that you want to create a traversal that walks through the entire Ast and does something special on just the Name nodes. And another traversal that does something special on just the integer literal nodes. One way to do this is to create a default traversal that walks through the entire tree without doing anything and then create a "subclass" that overrides just the visit_name and another that overrides just the visit_expr method.
One place that I've seen this in the wild is the Ocaml compiler:
The Visitor pattern is great if you want to process a data structure as "stream" without actually instantiating it, in the same way you can read a file line-by-line instead of loading it completely into memory.
For example, serde uses the visitor pattern to encode its intermediate representation. If it used pattern matching instead of the visitor pattern, it would have to instantiate its intermediate representation as an enum, which would add unnecessary overhead.
In section 2.10, "Privacy for extensibility", are there any pros and cons to this approach over using the #[non_exhaustive] attribute? The latter works on both enums and structs, and doesn't require extra fields to be included.
Using private fields you can more precisely control the “private scope”. #[non_exhaustive] is “crate scoped”, it does not apply limits for use in the same crate.
Private fields are by default module scoped, and can be tweaked. So you can limit the use even in the same crate.
Once you get past writing a language idiomatically, is a list of design patterns a good thing? It is an obvious negative for code readability because it reduces the number of people who can clearly understand your code from Rust users to Rust users who also memorize design patterns.
When are design patterns useful? And how are they useful?
Design patterns are intended to be a description of patterns found in the wild — that is, it’s descriptive, not prescriptive.
Once distilled to their essence, and documented, and named, it becomes possible to efficiently talk about it and reference it.
They are natural things, to be found in wild codebases, that are documented and named here. That they get used and abused, or people come up with terrible names (ClassFactoryFactory) is more a lack of taste than anything — but knowing that such patterns exist and their utility is a requirement of any tradesman seeking to move above novice. The name isn’t as much needed, but it makes it trivial to google.
But again, these are patterns found in the wild — it intends to document problems & solutions that programmers have encountered, and it’s a pattern because it comes up often enough — which implies that you will likely encounter similar problems and (perhaps discovered on your own) implement similar solutions.
Design patterns are not necessarily less understandable, much the contrary in fact. They basically encode known good ways to do something, so it should be okay even for beginners who haven't learnt the patterns yet.
I'm a bad man, and I have sinful [ooo] thoughts, but...
Is-a inheritance is extremely useful for creating extensible components. "It may
be wrong, but it's much too strong."
In rust, how can you make a component that is just like another component, but ever so slightly tweaked without copying the entire external API of that other component? I understand I can wrapper with has-a relationship, intercept the correct API, and then pass through the rest of the entire interface, but how can I avoid copying the entire interface of the object when I only want to tweak something tiny?
With a car, I can swap out the engine with another, I just have to make sure the external interface is the same.
It may be a "bad thing", but it is extremely useful for the scenario where I say, "I want a Chevy smallblock, but I want to only tweak metal alloy on the interior piston."
class MyBlock(ChevySmallBlock):
def get_interior_alloy(self):
return metals.Unobtanium
Bam. I have same item; slightly tweaked. I've used this type of pattern to great effect and I find that style of inheritance manipulation invaluable in python.
How can you do this with rust? I know that is-a inheritance is sinful, but show me the better way! I truly want to know it, and I've been trying to find a pattern for this.
Having documents like this is awesome - thanks for building it.
Is there somewhere I can PR? One example - the `new` constructor takes no arguments, so at minimum there should be a note about the (mentioned-next) Default pattern.
Also, I don't think Default is most useful for abstracting construction (I think closures are better for this), they're really just to make construction easier imo ie:
Some idiomatic suggestions seemed to replace lacking language features, which is a smell to me; I think it's best to use the language as designed instead of creating something weird and maybe even unstable like a "finally" block using the Drop trait for a dummy struct.
Design patterns were weird too. The builder pattern to hide complex initialization? Not a fan; maybe it would be best to remove the complexity instead? Rust is a functional language, so OOP patterns seem like an anti-pattern.
Patterns are nice, but dangerous to follow blindly in an immature and fast growing industry operating like a pop culture. As an example the book suggest to use YAGNI, a pattern/principle Jim Coplien suggest we fight. I’m inclined to agree. Plan ahead when you can. And btw pull in decisions to get early feedback in stead of delaying to the last responsible moment. Just some thoughts, but I like that patterns are collected for different contexts. It allows for discussion and learning.
[+] [-] codeflo|5 years ago|reply
[+] [-] tasn|5 years ago|reply
I added this to the top of https://github.com/etesync/etebase-rs/blob/master/src/lib.rs and then ran `cargo clippy`
Any idea what's missing? Why is it not failing?Edit: I know the above example is bad code, that's the point. I want clippy to complain about it but it doesn't.
[+] [-] brundolf|5 years ago|reply
This has been one of my biggest complaints about Rust: I've been using it for years at this point. I read most of the Book, I've read a few unofficial books. And I do love the language, but it has so many cases like this where things that you're allowed to do (syntactically or otherwise) are somehow so non-obvious that you can miss them entirely. I still get blindsided by something like this every couple months, and I always end up a little mad that I've been doing things the hard way until some obscure unofficial material (or more often, a stack overflow answer) teaches me about an entire feature that I didn't know existed.
Rust is really great at telling you what you can't do, and in many ways its documentation is incredibly thorough, but it has a real problem when it comes to discoverability and establishing a consistent mental-model of what its syntax actually means (and how you can then apply it to other situations). I don't know what the root cause of this problem is. But it's really distressing to me each time I discover a huge blind-spot; it makes me feel like I never fully understood the language concepts that I thought I understood.
I say all of this out of love: I really want Rust to succeed. I still prefer to use it despite this issue. I just believe this is a huge thorn in its side, especially when it comes to adoption, for which it already has an uphill climb.
[+] [-] staticassertion|5 years ago|reply
I'd say one way to a language more completely is to:
a) Read its stackoverflow pages (not sure about Rust but this helped a ton with C++)
b) Get code reviews from others. The rust community is fairly active, or at least it was back when there was just an IRC channel, I don't know as much now since I don't get onto whatever the medium is today. Ask people if there's an easier way to do something.
c) Read others code. I, for example, like to review some of my dependencies just so I understand a bit more about how they work, and I've picked up a lot from that. I used to get on IRC in the morning while I was kinda getting into my work-day and see if I couldn't help others out, this really taught me a lot.
[+] [-] steveklabnik|5 years ago|reply
[+] [-] ncmncm|5 years ago|reply
If the pattern could be captured in a library, that pattern would just be using the library. If the core language provided the feature, the pattern would just amount to using the feature.
Generally it is better to improve the language to the point where the feature can be added as a library component, but sometimes that is too hard. Thus, for most languages nowadays a "dictionary" type is provided in the core language, because a useful hash table library cannot be written with language primitives. In C, hash tables are open-coded again in each place where one is needed, because the language provides neither the feature, nor facilities sufficient to capture it in a library. Rust is powerful and expressive enough that hash tables are library components.
Conversely, pattern matching and coroutines are built into Rust. It should never be forgotten that (1) this was because the language was not expressive enough to capture the features satisfactorily in a library; and that (2) it would be better if, someday, the core feature became unnecessary because the language became expressive enough provide it as a library.
One reason it is better for features to be provided in a library is that another library can implement a variation on the feature that might not be as widely useful as the core version, but is better tuned to a less-common but still important use.
Another is that users can invent whole new features by combining powerful primitives that the language designers would not have time, or possibly inclination, to implement themselves.
Thus, in a certain sense, all patterns are anti-patterns.
[+] [-] vbrandl|5 years ago|reply
Edit: one limitation of pattern matching is, that all values need a common supertype (e.g. be variants of the same enum in Rust, if we see each variant as a type and the enum as the common supertype. There is an RFC [2] to make enum variants accessible as types), while the visitor pattern could be implemented for any set of independent types. On the other hand, you then cannot have a typed collection/container that contains values of these types, so you'd need some common trait like `Visitable` so you could accept an `Vec<dyn Visitable>`.
[0]: https://rust-unofficial.github.io/patterns/patterns/visitor....
[1]: https://openjdk.java.net/jeps/8213076
[2]: https://github.com/rust-lang/rfcs/pull/2593
[+] [-] ufo|5 years ago|reply
For example, suppose that you want to create a traversal that walks through the entire Ast and does something special on just the Name nodes. And another traversal that does something special on just the integer literal nodes. One way to do this is to create a default traversal that walks through the entire tree without doing anything and then create a "subclass" that overrides just the visit_name and another that overrides just the visit_expr method.
One place that I've seen this in the wild is the Ocaml compiler:
* https://github.com/ocaml/ocaml/blob/trunk/parsing/ast_iterat... * https://github.com/ocaml/ocaml/blob/trunk/parsing/ast_mapper...
[+] [-] daxvena|5 years ago|reply
For example, serde uses the visitor pattern to encode its intermediate representation. If it used pattern matching instead of the visitor pattern, it would have to instantiate its intermediate representation as an enum, which would add unnecessary overhead.
[+] [-] Twisol|5 years ago|reply
https://doc.rust-lang.org/reference/attributes/type_system.h...
[+] [-] michalhosna|5 years ago|reply
Private fields are by default module scoped, and can be tweaked. So you can limit the use even in the same crate.
[+] [-] varajelle|5 years ago|reply
[+] [-] ritchiea|5 years ago|reply
When are design patterns useful? And how are they useful?
[+] [-] setr|5 years ago|reply
Once distilled to their essence, and documented, and named, it becomes possible to efficiently talk about it and reference it.
They are natural things, to be found in wild codebases, that are documented and named here. That they get used and abused, or people come up with terrible names (ClassFactoryFactory) is more a lack of taste than anything — but knowing that such patterns exist and their utility is a requirement of any tradesman seeking to move above novice. The name isn’t as much needed, but it makes it trivial to google.
But again, these are patterns found in the wild — it intends to document problems & solutions that programmers have encountered, and it’s a pattern because it comes up often enough — which implies that you will likely encounter similar problems and (perhaps discovered on your own) implement similar solutions.
[+] [-] bonzini|5 years ago|reply
[+] [-] ldiracdelta|5 years ago|reply
Is-a inheritance is extremely useful for creating extensible components. "It may be wrong, but it's much too strong."
In rust, how can you make a component that is just like another component, but ever so slightly tweaked without copying the entire external API of that other component? I understand I can wrapper with has-a relationship, intercept the correct API, and then pass through the rest of the entire interface, but how can I avoid copying the entire interface of the object when I only want to tweak something tiny?
With a car, I can swap out the engine with another, I just have to make sure the external interface is the same.
It may be a "bad thing", but it is extremely useful for the scenario where I say, "I want a Chevy smallblock, but I want to only tweak metal alloy on the interior piston."
Bam. I have same item; slightly tweaked. I've used this type of pattern to great effect and I find that style of inheritance manipulation invaluable in python.How can you do this with rust? I know that is-a inheritance is sinful, but show me the better way! I truly want to know it, and I've been trying to find a pattern for this.
[+] [-] staticassertion|5 years ago|reply
Is there somewhere I can PR? One example - the `new` constructor takes no arguments, so at minimum there should be a note about the (mentioned-next) Default pattern.
Also, I don't think Default is most useful for abstracting construction (I think closures are better for this), they're really just to make construction easier imo ie:
edit: It's hosted on github, duh, nvm[+] [-] boomer918|5 years ago|reply
Design patterns were weird too. The builder pattern to hide complex initialization? Not a fan; maybe it would be best to remove the complexity instead? Rust is a functional language, so OOP patterns seem like an anti-pattern.
[+] [-] techsin101|5 years ago|reply
I'm attracted to Go, but at the end of the day I can do everything that Go can with Nodejs minus the speed part.
Rust has just way to many new concepts to simply learn it.
Why did you learn Rust over Go. How are you using it?
[+] [-] acje|5 years ago|reply