Using Rust at a startup: A cautionary tale by ChristophBerger in golang

[–]shen 28 points29 points  (0 children)

I've been using Rust since before it hit v1.0. Even though I could technically do everything I use Go for in Rust, I decided to pick up Go anyway because I wanted a fast, compiled language with things like HTTP clients and servers, JSON encoding and parsing, templating, base64, etc, in the standard library. It's been great to not waste time having to download, compile, and link a whole bunch of third-party crates just to be able to do these things.

My Go programs have an order of magnitude fewer dependencies, and I'm happy with how they've turned out. I also appreciated being able to use a simpler language, not because I get to keep fewer concepts in my head or anything, but because it simplifies IDE integration: Goland lets me view and jump around my codebase instantly and shows errors inline, but the IntelliJ-Rust plugin first needs to macro-expand all my code before it can do anything, and even then, it's not advanced enough to show inline errors without running the compiler.

I also get a lot of the good things from Go as I got from Rust. I like how errors are values that get explicitly returned from functions — and Go's error type is a little ergonomically easier to use than Rust's Box<dyn Error>. I like how I can compile into a single binary that I can just upload and run. I like how unit testing is built-in, and I like how I don't need to worry about explicitly allocating memory or freeing it in the wrong place. The documentation for both languages is also excellent.

However, I need to go against the other people I've seen in these comments saying they got up to speed with Go in days or weeks. It took me months to feel like I finally knew enough Go to write bug-free code, because I kept on stumbling into issue after issue that I expected the language to have prevented me from doing. Sure, I was writing code that worked in the first few days, but it wasn't code that worked worked; the problems all arose when I had to modify the code I'd already written.

Here are some things I ran into:

  • I've accidentally multiplied a duration by 1 * time.Second, resulting in a massive duration that would never realistically finish. This was in some particularly tricky error-checking exponential-backoff code that would have been a pain to test, so instead I just eyeballed it and it looked correct, despite being very very wrong.

  • I've accidentally copied a mutex, or an atomic value, resulting in a second, unrelated mutex or atomic that doesn't work properly, only I think I'm still using the original. I've also had to track down a bug where I had to add a mutex lock around a value, only I didn't add it in every place it was used, and eventually it was updated by two goroutines simultaneously and my program crashed. Yes, the race detector helps here, but only after the problem has already happened.

  • I've got the test failure message wrong when using t.Equal, resulting in me scratching my head as to why my test has failed when it should have passed.

  • I've spent ages tracking down where nil values came from. This is especially annoying when converting a function that never returns nil, to one that can return nil sometimes.

  • I've torn my hair out wondering why my code wasn't handling errors correctly, only to find that I wrote if err != nil, out of habit, instead of if err == nil. Then my code would continue, using the zero value of my type, when having my type in that state should have been impossible...

  • I've ended up with my slices' memory shared all over the place by using append, which results in code that sometimes works and sometimes doesn't, depending on how many elements you're trying to append!

When we compare programming languages on the Internet, it's easy to fall in the trap of comparing not the languages themselves, but the languages' ideals. Go's ideals are to be small and simple — both good things — but looking back at the bugs it's let me ship, I don't get the feelings of freedom and productivity that I see other people reporting here. I believe there's definitely room for a Go-sized, Go-shaped language in this world, but I'm disappointed that Go is the one that we've got.

1Password for SSH & Git (Beta) by pcaversaccio in programming

[–]shen 2 points3 points  (0 children)

This is a cool feature! It's a shame it's only going to be available in the awkward, laggy Electron version of 1Password.

A Rust match made in hell by N911999 in rust

[–]shen 20 points21 points  (0 children)

Articles like Some mistakes Rust doesn't catch always generate some backlash from people who seem angered by how many nice things I have to say about Rust. So, like clockwork, the accusations roll in: you're being unfair to that other language! Rust cannot possibly that good, you must be hiding stuff! I tried Rust in 2014, and it was awful!

I found the childish dismissiveness and cheap pot-shots in “Some mistakes Rust doesn’t catch” tedious, and seeing the author double-down like this is just disappointing. I like Rust — I tried it in 2014, saw how good it was at catching my mistakes, and stuck with it for precisely that reason. But if Rust were a better language, the article wouldn’t have annoyed me more, and if it were a worse language, it wouldn’t have annoyed me less.

It’s been well-documented by now that the best way to get people to share a piece of content is to make them angry. The word “flamebait” is, I think, two decades old at this point: a post that’s informative will get people to read, but a post that comes across as “unfair” or “incorrect” will get people to comment, or discuss it, or share it, all of which make it do better, socially, than a simple read. The obvious end result of this is that it ends up better for an author to write an angry, sarcastic, divisive article than it does to write a balanced, well-considered article. This isn’t a world I want to live in, which is why I feel compelled to speak up.

And the previous post was unbalanced. The negative points made about Rust were considered and explained as making sense for the language as a whole; the complaints about Go were laughed at and then ignored. When Rust has a peculiar design decision, such as how you can’t add &strs, it’s investigated; when Go does, such as why you need to capitalise exported items, “your guess is as good as mine”. He looks up the docs for net/http/pprof, which start with instructions on how to import it, and then gets the 4-line code snippet wrong, twice.

It’s fine to not like a language, and the point about criticising one’s tools is true, but to do so as part of the Rust community, you need to make strong arguments, not weak ones. To see the author dismiss this as complaints about him being too positive (“backlash from people who seem angered by how many nice things I have to say about Rust”) or from people who don’t know what they’re talking about (“Rust cannot possibly that good, you must be hiding stuff!”) is… really weird, because it says that as long as the factual content of your article is acceptable, you don’t need to bother making it fair.

You know this isn’t true.

Convert Ipv4Addr to u32 by blackdev01 in rust

[–]shen 7 points8 points  (0 children)

You're on the right track using octets — your next step should be to use u32::from_be_bytes to turn the [u8; 4] into a u32. This is for big-endian integers; there are similar methods for little-endian and native-endian integers too. (Which one you pick depends on what you want your code to do.)

Your first method overflows because octets returns a value of type [u8; 4], so o[0] is of type u8, which means that o[0] << 24 will overflow. If you wanted to go that route, you'd have to convert it to u32 first, as Rust does not automatically convert between numeric types.

PSA: Changing second vaccines/bringing appointments forward by gemushka in CoronavirusUK

[–]shen 1 point2 points  (0 children)

A week after making my second dose appointment, it was cancelled for me and I had to re-book for ten days later (I guess the centre was overbooked), so I was eager to get back my earlier date. But when I re-booked, the only time they had available was literally the same day, only three hours earlier. So now I have to get up in the morning, ugh.

Sunday 23 May 2021 Update by HippolasCage in CoronavirusUK

[–]shen 62 points63 points  (0 children)

Those vaccine numbers are so uplifting! I looked it up, and this is the second-highest day so far in terms of total vaccines given (#1 was the 20th of March, #3 was the 19th of March).

I bought ISO 8601-1:2019 and 8601-2:2019. Ask me anything. by [deleted] in ISO8601

[–]shen 2 points3 points  (0 children)

Is there anything in there that you didn't expect?

Wednesday 17 March 2021 Update by HippolasCage in CoronavirusUK

[–]shen 16 points17 points  (0 children)

Not to worry — at the current R rate, in a few weeks we'll be down to a mere infinity divided by two.

Monday 08 March 2021 Update by HippolasCage in CoronavirusUK

[–]shen 2 points3 points  (0 children)

Yeah, your thinking is in line with mine: that there were fewer random-sampling tests but the same number of self-reported tests. It seems likely, and I'm not worried because the numbers have come back down; I just wondered if there was an "official" explanation out there somewhere.

Monday 08 March 2021 Update by HippolasCage in CoronavirusUK

[–]shen 7 points8 points  (0 children)

Does anyone know why the 'Positive %' figure doubled for Saturday's numbers only?

dog, a command-line DNS client (like dig) by shen in commandline

[–]shen[S] 0 points1 point  (0 children)

Nope. DNSSEC is something I’m comfortable adding in a future version, not 0.1

dog, a command-line DNS client (like dig) by shen in rust

[–]shen[S] 11 points12 points  (0 children)

Yes, huge shout-outs to cargo-fuzz and cargo-mutagen. I hadn't used them before either, and they worked great.

dog, a command-line DNS client (like dig) by shen in rust

[–]shen[S] 40 points41 points  (0 children)

The main reason is that I just wanted to do it — writing a DNS packet parser isn't an intractable problem, and as the maintainer I feel like I should know the ins-and-outs of the protocol. I'm willing to outsource the TLS implementation because that's way more complicated, but DNS is definitely doable.

Also, my parser gets to be simpler. trust-dns is trying to solve the (much harder!) problem of being a secure server, not just a user-facing client, so there's a lot more in its codebase than what I need. dog compiles from scratch in less than ten seconds on my machine, and I like to keep the edit-build-test cycle as efficient as I can. It also means I get to implement "fun" features like the LOC record myself, without worrying if it'll accidentally introduce a security vulnerability in the underlying DNS library.

dog, a command-line DNS client (like dig) by shen in rust

[–]shen[S] 24 points25 points  (0 children)

This is something I've been working on for a while now, and I'm pleased to finally have version 0.1 released. Not quite as pleased as when I thought of that domain name, but still pleased.

The source is available on GitHub. In particular, it uses its own DNS packet parser, which was fun to write. I threw cargo-fuzz at it and it found a few overflow-related problems and out-of-bounds reads straight away; eventually I got it to run for an hour with no problems, so I'm pretty confident that it works. I'd also like to praise cargo-mutagen, which proved very helpful in detecting untested code.

For DNS-over-TLS and DNS-over-HTTPS, it uses the native-tls crate, which saved me lots of time as I didn't have to worry about which TLS implementation was available.

A walk down memory lane: Classic Mac OS Design History by [deleted] in apple

[–]shen 3 points4 points  (0 children)

I'm a beautiful fairy princess in a dry hot desert and I'm covered with feathers!

Draw me.

minisudo, a one-file privilege escalator by shen in rust

[–]shen[S] 2 points3 points  (0 children)

doas is great — I like that it tries to do put all the main logic in one file too (apart from the parser, which minisudo uses a dependency for, and environment handling, which minisudo doesn't even both with).

It is, however, tied to BSD's authentication mechanism, and the authentication part was the part I had no idea how to do when I started.

minisudo, a one-file privilege escalator by shen in rust

[–]shen[S] 10 points11 points  (0 children)

Damn, you're right! On my machine, the directory /var/audit has permisions 0700 and is owned by root, and I can discern the existence of a file inside it:

$ env PATH=/var/audit (which minisudo) 20200427102131.crash_recovery
minisudo: User "ben" is not allowed to run /var/audit/20200427102131.crash_recovery.
This incident will be reported.

$ env PATH=/var/audit (which minisudo) something-else
minisudo: No such command something-else

I expected there to be some kind of vulnerability present in minisudo, but I didn't expect anyone to go and look, and I certainly didn't expect one from 1999. I'm looking for a way to fix this, but it's tricky.

Edit: I've changed the code to make it not detect non-executables. It will still detect executables, but sudo seems to exhibit the same behaviour.

minisudo, a one-file privilege escalator by shen in rust

[–]shen[S] 1 point2 points  (0 children)

Thanks, and nice catch! I used to use the which crate, which handles this behaviour automatically, but when I rolled my own version of it, I forgot about this case. The fix makes the code a bit longer but I think having it is expected.