#![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.I added this to the top of https://github.com/etesync/etebase-rs/blob/master/src/lib.rs and then ran `cargo clippy`
#![warn(clippy::all)]
// Should fail https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp
pub fn bool_test(x: f32, y: f32) -> bool {
x == y
}
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.
To my knowledge (it's been a while since I looked) fixing this behavior is blocked on cargo stabilizing something and has been for literal years.
That point of frustration aside, it's worth it...Clippy is an absolutely amazing piece of software. Both for pedagogy and normal development.
EDIT: Just dug up the issue. If you're on nightly you can use `cargo clippy -Z unstable-options` to avoid the clean/rebuild. Hopefully stuff gets stabilized soon. Here's the issue for reference: https://github.com/rust-lang/rust-clippy/issues/4612
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.
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.
I have wanted that feature at times in the past (or something like it), and wasn't able to determine that it would be possible. That's exactly my point.
As for your suggestions: this is indeed one way to gather this knowledge, and I have learned a lot particularly from looking at stack overflow. But I don't think it's ideal when knowledge about fundamental language features has to be acquired by word-of-mouth, because it wasn't conveyed in the course of the normal learning path (official tutorials + discovery through application of learned concepts to new situations where their relevance is self-evident).
Here's the train of intuition:
1) dyn requires a pointer that may be to one of multiple types of structs
2) a group of multiple types of structs has an undefined memory layout, so the value must either live on the heap or be wrapped up in an enum
That feels like an airtight understanding. But then Rust lets you do this weird juggling maneuver based on control-flow that allows you to do it on the stack.
I'm not saying Rust shouldn't let you do this, and I'm not really sure how it could be made intuitive given the "normal" case. I'm just expressing that subjectively, this feels very weird and non-obvious, and it's far from the first example like this that I've encountered. Here's another example: https://news.ycombinator.com/item?id=25595120
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.
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....
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...
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.
Don't get me wrong. I'm sure, the serde developers know better than me and have good reasons to implement it the way they did, but I'd like to understand the rational behind the decision.
Edit: formatting
[0]: https://github.com/serde-rs/serde/blob/master/serde/src/de/m...
https://doc.rust-lang.org/reference/attributes/type_system.h...
Private fields are by default module scoped, and can be tweaked. So you can limit the use even in the same crate.
When are design patterns useful? And how are they useful?
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.
So the movement of a door changes how the window should be placed, which changes the best position of the sofa, which changes the ideal spot for the door, and so on. So it’s a feedback loop, approaching some optimal state where everything is in harmony with everything else (a state he calls “beautiful”, an inherent property all people recognize even if they cannot produce it themselves) — but because people live in the home, and use it and modify it and change through generations, that harmony is a moving goalpost, always sought but only possible to achieve once abandoned.
The analogy to software development is easy to make, but the key point here is that in usage, patterns are not the end but rather the beginning of a design — they can and should be modified to fit, until it is beautiful
In my experience there is at least a faction of developers, myself among them that have a disdain for "design patterns thinking." Which I would describe as: spending a lot of focus learning various design patterns, then while coding actively looking for places where those patterns could be put to use.
In my opinion this is an anti-pattern similar to overuse of abstraction in simple cases before an abstraction adds to the understanding of the code itself, rather than makes the code more complex.
I've seen lists of common design patterns dozens of times, and occasionally recognize several of them as useful examples of things I've actually done in the past or my colleagues have done in the past. But it seems to me "learning design patterns" as an end is encouraging the destructive side of design patterns where you learn something and eagerly look for a use for it.
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.
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:
Foo {
prop: override_default,
...Default::default(),
}
edit: It's hosted on github, duh, nvmDesign 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.
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?