Conversation
|
Note to self: Should mention https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=c1c8e31332776002d7030e2e242ebcee somewhere, which is the entirety of all Rust keywords in under 300 characters. Source: https://www.reddit.com/r/rust/comments/1ns8mxg/all_48_rust_keywords_in_under_300_characters/ |
M3t0r
left a comment
There was a problem hiding this comment.
There are some changes I'd make, but overall I think it's a good post 👍
| Whenever I see people struggle with Rust syntax, I'm reminded of the [five stages of grief](https://en.wikipedia.org/wiki/Five_stages_of_grief) by Elisabeth Kübler-Ross: | ||
| It's a common framework for understanding how people deal with loss, but I think it's a great analogy for how stubborn developers react to their first encounter with Rust. |
There was a problem hiding this comment.
The Criticism section is about as long as the description of the model itself. With "All models are wrong, but some are useful", this one isn't even that useful.
This whole section reads like a bad arm-chair diagnosis of people you haven't met. It could be slightly rewritten to come off as a humours take instead.
As it stands, I'd rather remove it completely than publish it unchanged.
|
|
||
| Finally, developers begin embracing idiomatic patterns and the philosophy behind Rust. | ||
| They refactor their spaghetti code and reach for stronger types rather than resisting them. | ||
| Code becomes more maintainable, and they wonder how they ever wrote code in other languages without tooling that gives you such confidence. |
There was a problem hiding this comment.
You can write Rust code in any language too. You just don't get many of the safety features ;-)
| **Ugly Rust code is a symptom of old, bad habits.** | ||
|
|
||
| Based on this realization, we can systematically improve the code. | ||
| While we go through the refactoring, keep in mind that there is no single "right" way to improve the code, but that it all depends on the context and your goals. |
There was a problem hiding this comment.
We are 200 lines into an 850 line post, and just now you've set expectations for the rest of the post: we will refactor a common problem into idiomatic Rust and explain a generic approach along the way.
It's a lot of text to read to get to this point. If I share this with someone to help them improve their code, they have a lot of stuff to skip.
| Note that we shadow `line` with `line.trim()`. | ||
| That is a common practice in Rust and very useful to keep the code clean. | ||
|
|
||
| It means we don't have to come up with a fancy new name for the trimmed line | ||
| and we also don't have to fall back to cryptic names like `lref` or `l` instead. |
| Granted, our code has gotten quite a bit more verbose again. | ||
| But in comparison to the original code, the verbosity has a purpose: it marks the various bits and pieces of our code that can go wrong. | ||
| We have agency to decide how to handle these errors gracefully on the call site rather than silently ignoring them. |
There was a problem hiding this comment.
This is the perfect place to mention thiserror, to reduce the verbosity to the bare minimum while still retaining the expressiveness of which errors can happen in our application.
While giving pointers into the ecosystem, it also takes a bit of the repulsiveness of "I have to write so much to gain so little".
| match KeyValue::try_from(line) { | ||
| Ok(kv) => { config.insert(kv.key, kv.value); }, | ||
| Err(ParseError::InvalidLine(_)) => continue, // Skip invalid lines | ||
| Err(e) => return Err(e), |
There was a problem hiding this comment.
For novice readers it might not be obvious that this is catching all errors other than InvalidLine.
| Err(e) => return Err(e), | |
| Err(e) => return Err(e), // Fail on any other error |
|
|
||
| // Examples of parsing from different sources | ||
| fn parse_str(input: &str) -> Result<EnvConfig, ParseError> { | ||
| Self::parse(input.as_bytes()) |
There was a problem hiding this comment.
as_bytes? It looks like the parse function takes a String reader. Otherwise, why would we have to implement TryFrom<String> above?
It's super confusing that the reader is bytes based, and that the line iterating function handles the String conversion too.
| ``` | ||
|
|
||
| Phew, that was a lot of code, but look how much more maintainable and extensible it is now! | ||
| We could even go one step further and make the `EnvParser` struct implement `Iterator` so that you can iterate over the parsed key-value, |
There was a problem hiding this comment.
Maybe link to the iterator trait?
| - On Unix, `key=value=more` is valid and everything after the first `=` is part of the value | ||
| - Indented lines with leading whitespace should be parsed normally | ||
| - Duplicate keys should overwrite earlier ones with later values | ||
| - Quoted values like `key="value"` keep their quotes in our solution |
There was a problem hiding this comment.
That is not an edge case! That's a choice! And I'd argue the wrong one for a functional parser.
| - Quoted values like `key="value"` keep their quotes in our solution | |
| - Quoted values like `key="value"` should not include the quotes in the value |
Co-authored-by: Simon Brüggen <github@m3t0r.de>
Note to self: rename to "Ugly Rust" 😉