Skip to content

When Rust Gets Ugly#73

Open
mre wants to merge 31 commits into
masterfrom
ugly
Open

When Rust Gets Ugly#73
mre wants to merge 31 commits into
masterfrom
ugly

Conversation

@mre

@mre mre commented May 1, 2025

Copy link
Copy Markdown
Member

Note to self: rename to "Ugly Rust" 😉

@mre

mre commented Oct 11, 2025

Copy link
Copy Markdown
Member Author

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/
The idea would be that there isn't that much syntax to learn.

Comment thread content/blog/ugly/index.md Outdated
Comment thread content/blog/ugly/index.md
Comment thread content/blog/ugly/index.md Outdated
Comment thread content/blog/ugly/index.md Outdated
Comment thread content/blog/ugly/index.md Outdated

@M3t0r M3t0r left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some changes I'd make, but overall I think it's a good post 👍

Comment thread content/blog/ugly/index.md Outdated
Comment on lines +166 to +167
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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +311 to +315
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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

Comment on lines +493 to +495
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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For novice readers it might not be obvious that this is catching all errors other than InvalidLine.

Suggested change
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())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is not an edge case! That's a choice! And I'd argue the wrong one for a functional parser.

Suggested change
- 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants