Skip to content

jamesqh/rusty-protohackers

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 

Repository files navigation

About

https://protohackers.com/ is a series of 12 challenges involving writing servers to specifications and hosting them so that a remote test suite can check their correctness. They involve wrangling TCP and UDP, implementing custom presentation layer protocols, complex concurrent behaviour, and vigilant investigation of edge cases. They're fun, which is why despite completing all of them years ago in Python (to the tune of 17th place in the leaderboard), I've recently done them all again in Rust - and Rust lends itself far more easily than Python to tidy, sensible-looking, flauntable code.

I've trimmed dependencies to a minimum (hence the avoidance of thiserror and anyhow and any form of logging, and the liberal use of unwrap - with these challenges, if we get an unexpected error there's really nothing useful we can do other than crash), but the project does demand nightly at the time of writing. A simple cargo run [challenge_number] [port] should work; omitting port will fall back to the default port defined in main.rs, as will omitting challenge_number.

Misc thoughts

Closing TCP connections is annoying

It really seems like when you have a TcpStream that you have flushed - a blocking operation! - you should be able to drop it and trust that your peer will receive all the data and the disconnect will not cause issues. Not so. On several challenges, the test suite complained about not receiving Errors, when I had obediently written the errors and flushed them before dropping the stream. In one case it was fixed by adding a shutdown(std::net::Shutdown::Write) before the drop; in another, shutdown was insufficient and unnecessary, and I instead had to read first, despite not wanting or needing the data. Both very frustrating problems.

BufRead::read_until is tricky

It seems to me reasonable to expect read_until to not return until it finds the delimiter, and to eventually time out if it doesn't. The docs make clear that this isn't so, "Reads all bytes into buf until the delimiter byte or EOF is reached", but despite seeing this that was still basically my intuition. But the effect of the specified behaviour is that when you read_until on a reader that's reached EOF, it just immediately returns Ok(0), which was very much not in accordance with my intuition, and it wasn't long before I was having problems with a read_until loop that just kept running forever even though the stream was long exhausted, successfully reading 0 bytes over and over again. This is a case that needs to be handled carefully!

Channels are great, async is overrated

I deliberately avoided using tokio in favour of figuring out how to do multithreading properly, and I didn't regret it. Channels and message passing feel like a really natural way of approaching these problems and I only occasionally had to do gnarly Mutex/Condvar things.

Newtypes lead to enlightenment

It's just so good to have the type system keep track of whether values have come from where you expect them to, a whole category of bugs becomes impossible. At one point I was toying with writing a double-ended proxy for debugging purposes, and because it was only for debugging I was lazy and used usize for stream indices rather than making newtypes. Being double-ended, some clients connected externally from the internet and some connected internally from my desktop, and I was just passing around usizes to identify them in the naive belief that I'd only use IDs from each type of client in ways that would make sense.

Inevitably, there was a bug, and while seeing if Claude could figure it out, it suggested a line where it thought I might have confused the two index types. I knew I hadn't, but to appease it I refactored to generate them as InternalClientStreamId and ExternalClientStreamId, and I was right, the line Claude was suspicious of was fine - but elsewhere I had mixed them up and that was what was causing the problem.

Use newtypes!

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages