Hacker News new | past | comments | ask | show | jobs | submit login
Learning Rust via Advent of Code (forrestthewoods.com)
286 points by forrestthewoods on Feb 4, 2019 | hide | past | favorite | 83 comments

> I feel like there should be a helper for simple parse operations.

> parse!("#{} @ {},{}: {}x{}", id, x, y, w, h);

> This would be a clean inverse of println!.

The text_io crate does this exactly.


    extern crate text_io;

    fn main() {
        let id: u32;
        let x: u32;
        let y: u32;
        let w: u32;
        let h: u32;
        scan!("#{} @ {},{}: {}x{}", id, x, y, w, h);

This is "stolen" from r/rust. But there's also a `scan!` macro in serde_scan[1]

    let line = "#1 @ 555,891: 18x12";
    let parsed = scan!("#{} @ {},{}: {}x{}" <- line)?;
[1] https://docs.rs/serde_scan/0.3.2/serde_scan/macro.scan.html

Ohhh, now that's super handy.

I wonder if we'll ever see `scan!` in the STD lib...

I hope not.

With a healthy crate ecosystem, std should be as small as possible (without being too small).

It makes sense for std to contain:

- Types that can't easily be put in a module (eg the `Fn` trait)

- Types needed for cross-project interoperability (like `TcpStream` or `Future`). Having these in std helps prevent ecosystem fragmentation.

- Stuff that gets used in most non-trivial projects (eg `Box`, `Vec`, `println!`)

Everything else belongs in a crate, where it won't bloat up the size of rust's standard library for everyone in perpetuity. `scan!` looks great; but I expect it'll be used in less than 1% of projects. There's no shame in keeping it in a crate - thats what they're for!

I see this come up a lot for various things in the Rust ecosystem. I think that moving more things into the standard library would actually be a net loss, mainly due to the versioning being tied to that of Rust itself. Once something is moved to the standard library, there can never be any breaking changes unless Rust 2.0 becomes a thing (which currently the core team says is not going to happen). It also makes the burden of doing bugfix releases much higher; for smaller bugs, users will have to wait until the next release of Rust itself, and for larger bugs, the entire language will have to do another release ahead of schedule. Given how easy Cargo makes it to add a dependency and how few projects can't actually use Cargo, I think it's better for the overwhelming majority of the ecosystem to keep things like this out of the standard library.

If you decide to learn Rust using AoC 2018, I highly recommend comparing your work with that of BurntSushi, who is known to write high quality code.


You develop Rust skills by writing your own and then hone your skills by learning from idiomatic Rust.

I learned Go first and then went to Rust, which was convenient because it appears sushi went the same route.

I suspect his current route includes both Rust and Go. :)

Same with mine.

Fellow Gopher here interested in learning Rust. Any recommendations?

The Rust book is really good.


It's _so_ good! It's the best technical writing I've read in memory. It's outstanding.

He links to BurntSushi in the "Parsing Text is Hard" section!

For sorting sequential fields you can chain comparisons with .then():

    struct Date {
        year: u32,
        month: u32,
        day: u32,
    let mut vec: Vec<Date> = Vec::new();
    vec.sort_by(|a,b| {
Alternatively you can derive PartialEq, PartialOrd, Eq and Ord for your struct, which will produce a lexicographic ordering based on the top-to-bottom declaration order of the struct's members:

    #[derive(PartialEq, PartialOrd, Eq, Ord)]
    struct Date {
        year: u32,
        month: u32,
        day: u32,

Or you can use sort_by_key and extract the relevant sorting key as a tuple (or any other Ord structure) e.g.

    vec.sort_by_key(|d| (d.year, d.month, d.day))
sort_by is more flexible as it works fine with borrows, but when sorting on a series of integer values or references sort_by_key is great.

> Alternatively you can derive PartialCmp for your struct, which will produce a lexicographic ordering based on the top-to-bottom declaration order of the struct's members:

Do you mean PartialOrd? partial_cmp is the method. And `sort` requires absolute ordering (Ord) not just partial.

I quite like this tuple approach.

And yeah, I meant Ord (and the required other Traits), I edited my post. Thanks for pointing this out.

"Learning a new language via Advent of Code turned out to be a great idea. I highly recommend it."

I'll second this: Using a set of progressive problems is an excellent way to get used to actually writing code in a language.

"My first stumbling block was parsing. Almost every AoC problem starts with parsing lines of text from an input file."

Scheme. Guile. The PEG parsing module. (There's no kill like overkill.)

> I'll second this: Using a set of progressive problems is an excellent way to get used to actually writing code in a language.

IME AoC isn't really progressive though, the problems ramp up and down pretty dramatically.

> Scheme. Guile. The PEG parsing module. (There's no kill like overkill.)

Rust has several pretty good parsing libraries e.g. nom, lalrpop, pest, …

I've been using Kattis problems as a way to work on my Rust skills. The problem there is you're limited to the standard library. Rust's ultra-minimal stdlib makes a number of those kattis problems remarkably hard to implement, where I've managed to whip up something in short order in Python using the stdlib.

I keep thinking that Rust has gone a little too far on the "keep it minimal" front. It also really disturbs me just how often with rust I'm having to rely on third party hosted crates. That's one huge amount of trust going on there.

Part of what we do to balance this out is to have the team make some of those third party packages; the regex crate is a good example of that. It’s still maintained by the team even if it’s not in the standard library itself.

So then why isn't the regex lib part of the standard library?

The standard library is locked to Rust - you can't issue a new major version of stdlib without having a major version of Rust.

Besides the impact of not being in stdlib is very low except for discovery.

> The standard library is locked to Rust - you can't issue a new major version of stdlib without having a major version of Rust.

More importantly, due to Rust's backwards compatibility guarantees, you can never remove something once it has been added.

I vaguely wave towards Python's 3-5 built-in "get data from a URL" libraries as an example of a bad route that this can take.

The regex crate can release a backwards-incompatible version 2 without destroying the entire ecosystem since multiple versions of a crate can co-exist in the final graph of dependencies.

> The standard library is locked to Rust - you can't issue a new major version of stdlib without having a major version of Rust.

How often do you see that actually being a problem? Rust releases new versions on a regular cadence. Just how often do you imagine the regex crate actually needs to be updated? Or how about the random number generator crate?

> Besides the impact of not being in stdlib is very low except for discovery.

I always find this an interesting argument. We've seen node.js etc. packages be compromised many times, or even completely vanish (left-pad)? How much confidence can I have that any individual package hasn't been compromised somehow? How much confidence can I have that random dependencies aren't suddenly going to enter my build chain. That left-pad situation was classic. So many things got broken, not because they'd picked up left-pad, but because dependencies of dependencies of dependencies relied on it (and so on down the line...)

There was that situation just a month or so ago where a developer just didn't want to maintain their package any more, had someone volunteer, who then compromised it with a crypto-miner.

Now on top of that licensing gets to be a whole bunch of fun as soon as you step outside the stdlib. For every crate you add, you need to do a licence audit, and for each and every one of its dependents, and its dependents dependents. Amazon, for example, has a black list of licenses. You can't use any software licensed under one of them, for whatever reason the lawyers have about each one.

> How often do you see that actually being a problem?

See my sibling comment about backwards compatibility

> Just how often do you imagine the regex crate actually needs to be updated?

You can see the frequency of updates to the regex crate if you are interested: https://crates.io/crates/regex/versions.

Sometimes a release in a few days, or a few a month.

> Or how about the random number generator crate?

Even more interesting, because `rand` hasn't even reached 1.0 yet! https://crates.io/crates/rand/versions

Specifically, the authors are still deciding the right way to architect the library for the myriad of uses that Rust has.

> or even completely vanish (left-pad)

In 99.99% of the cases, you cannot remove a crate from crates.io; you can only prevent new projects from adding the crate as a dependency. The other 0.01% is because of legal reasons, and there's not much to be done about that.

> For every crate you add, you need to do a licence audit

https://github.com/onur/cargo-license claims to show you the licenses of every dependency. It's required to have a license to publish to crates.io.

Your concern may be founded in the difficulty other languages' package managers present. With regard to C/++, there really isn't package management at all, making package inclusion a big decision. With regard to Ruby (for example), the package management is actually pretty unreliable and terrible given how many gems require system libraries and incompatibilities of gems across Ruby versions.

With Rust, the package manager is very good. So it's not a problem in practice.

Some comments having flown through the article

> Helper Lambdas

> I like lambdas in C++11. I use them regularly for small helpers that exist solely within a function.

`move` lambdas might work better? The default tries to infer based on usage but often fails. A `move` lambda lets you do something similar to C++'s capture lists (though more verbose / cumbersome).

> BinaryHeap

> The standard library provides a max-heap. I regularly needed a min-heap. I got one by making a custom struct with custom compare function.


> I wonder if there is a nice macro crate to help with this? I'd love to write: compare!(a, b, year, month, day, hour, minute);


> I wish there was an f16 half-precision float. There's a good internals thread on minifloats that makes me think f16 will happen eventually.

A problem's there is there are at least two "standard" f16 (IEEE and ML)

> There is NonZeroU32. It's similar-ish, but only works for zero. There isn't a Non255U8 or, thankfully, Non4294967296U32.

They're built from the unstable `NonZero` (and the unstable and unsafe ZeroAble).

You could build a struct on top of NonZero which swaps zero and your sentinel value.

The #[rustc_layout_scalar_valid_range_start(...)] and #[rustc_layout_scalar_valid_range_end(...)] attribute allows you to specify a niche, so you can build a Non255U8 if you want. For example:

#[rustc_layout_scalar_valid_range_end(254)] struct Non255U8(u8);

Slightly OT: this seems like a good way to learn to program. My wife has been trying to learn to program with python but all the tutorials even programming 101 tutorials these days actually seem more advanced than I would expect. For example they assume someone gets what a string is and how it works with surprisingly very little explanation, then overload the student by comparing the difference between string objects in Python 2 vs Python 3 and Unicode va non-Unicode! Woah object, Unicode, what?

Are there any good resources for progressively learning Python by doing something like AOC but starting from nothing and as a tutorial and explaining the basics of what a string is etc as it goes?

codingbat.com, the Python exercises. They have all the background information needed in nearby links (e.g. I have an exercise with strings how do I use those), and build experience nice and easy

I also thought regex seemed overkill and didn't feel like adding an extra crate to my Cargo.toml. Luckily for the days I attempted I was able to get by on just the split method for str's[0] which was nice and concise. So for your regex example you could also do:

    // #1 @ 916,616: 21x29
    let parts: Vec<&str> = l.split(['@', ',', ':', 'x'].as_ref()).collect();
    let x = parts[1].trim().parse::<i32>().expect("x as i32");
    let y = parts[2].trim().parse::<i32>().expect("y as i32");
    let w = parts[3].trim().parse::<i32>().expect("width as i32");
    let h = parts[4].trim().parse::<i32>().expect("height as i32");
[0] https://doc.rust-lang.org/std/primitive.str.html#method.spli...

The next step could be to remove `.collect()` and use `.next()` for each subsequent part. Zero allocations!

    let mut parts = l.split(&['@', ',', ':', 'x'][..])
       .flat_map(|s| s.trim().parse::<i32>().ok());
    let x = parts.next().expect("x as i32");
    let y = parts.next().expect("y as i32");
    let w = parts.next().expect("width as i32");
    let h = parts.next().expect("height as i32");

> and didn't feel like adding an extra crate to my Cargo.toml

Why not? cargo makes it extremely easy to add new crates. The hardest part is figuring out what dependency you want, but once you know what it is, adding it is really easy.

Totally agree, cargo makes it easy. Honestly it’s just that the level of laziness is so high when I’m programming on something personal after work that even having to switch to chrome to check a version number on crates.io will be avoided.

You might be interested to know that cargo can do that for you

  $ cargo search regex
  regex = "1.1.0"             # An implementation of regular expressions for Rust. This implementation uses finite automata and gua…
  regex-automata = "0.1.5"    # Automata construction and matching using regular expressions.

This is awesome. How did you figure this out? The documentation on `split` says:

"The pattern can be a &str, char, or a closure that determines the split."

But in your case it is an array of chars and it splits for each of them. I don't see this documented at all.

The docs are a tad misleading but the important piece is to look at the signature. The `where P: Pattern<'a>` portion states that split needs a type that implements the Pattern trait. If you follow the link to the Pattern trait then at the bottom you'll see a section[0] listing types implementing the trait (&[char] being one) and therefore can be passed into split. Hope that helps!

[0] https://doc.rust-lang.org/std/str/pattern/trait.Pattern.html...

Would you be willing to file a docs bug so we can clarify this?

It’s possible that it used to be only those types, but was expanded. We should fix this!

Yeah, will do

Awesome, thank you!

Vec<char> or an array for instance - [char; 20] can (automatically) create a "slice" of type &[char]. This is true for all types (not just char).

The operator for this is for eg l[1..10]. It can also happen automatically.

&str is the same type as &[char] - just a renaming.

Still learning Rust myself, apologies if this leads you astray. Just do some reading on slices.



&str is not the same as &[char]. &str has the same memory representation as &[u8]. char is four bytes, not one.

Thanks Steve.

No problem! It's an easy mistake to make.

I did this year's AoC in Ada. (Well, part of it, I didn't do the whole challenge.) I'd never written any serious Ada code before, and this was a great way to get a better feel for it. Overall a positive experience! Ada can be a bit verbose at times, but it wasn't nearly as bad as I had expected it to be... Once you learn not to fight against the grain of the language, it's just a matter of decomposing the problems in an Ada-ish way.

Suprisingly I found it very constructive to work on "two puzzles at once" -- how to use the new language, and solving the AoC itself.

This is slightly off-topic, but someone mentioned that it would be a good idea to check out BurntSushi's solutions after trying it yourself which seems like a fantastic idea.

It would be cool if there was some resource that linked well-written, idiomatic solutions for other languages as well. Anyone know if this has been done?


Not all repos are idiomatic, there’s at least one that isn’t (mine).

I found some of the repos useful for bettering my rust skills.

@forrestthewoods You have an incorrect link under the "Advent of Code" section. The first link reads

It should just be https://adventofcode.com/


I'm trying to do the exact same thing (learning Rust through solving the AoC) but I'm not done yet.

Regarding parsing, a lot of times the input format is unnecessarily complex. In "real-life", wouldn't you start by sanitizing your input? For me, search/replace and column-edits have been enough to avoid regexps entirely.

Agreed. Most of my solutions[0] use something simple like

    .map(|s| s.trim())
    .filter(|l| l.len() > 0)
or for grid-like puzzels

    .map(|s| s.trim())
    .filter(|l| l.len() > 0)
    .map(|l| l

0: https://github.com/k0nserv/advent-of-rust-2018

As someone who also completed Advent of Code in Rust this year this was very interesting. I agree it is a great way to learn more about a new language. Thanks for sharing!

It seems like we ran into a lot of the same pain points like parsing, lack of a min-heap, etc.

Regarding HashMap initialization the best way I'm aware of to do what you are looking for is with lazy_static[0]. Maybe not very clean or idiomatic though.

For sorting I found the best solution to that problem was using sort_by_key with a tuple like (a.year, a.month, a.day, a.hour, a.minute).

[0]: https://github.com/rust-lang-nursery/lazy-static.rs#example

I use a different approach to learning new languages: I try to find out why the language really exists. Often, there is a very good reason that justifies all the work of creating a new language.

Often, this has more to do with the environment, runtime, etc., and the features of the language merely exist to serve that purpose. If that were not true, someone probably would have used an existing language and changed the runtime.

For rust, it seems to exist to bring modern, safe language features to a minimal C-like runtime existence by doing all of the work in the compiler. C has continued to thrive all of this time because it has a minimal runtime and a stable ABI defined on nearly every platform. Rust has a minimal runtime and supports the C ABI very well, so it has a great chance to have a big impact in areas where C/C++ might otherwise be used less safely and less productively. So my focus when learning rust is how to use the C FFI while still writing idiomatic rust.

It's harder to learn that way, but I find it more satisfying, especially in cases where I don't have an immediate project that's going to use the language. The fact is, no matter how many programming puzzles I solve in rust, it's not going to make it any more likely that I will rely on it for anything but a toy or hobby project. But learning the rust FFI does make it more likely I will rely on it in the future. Puzzles can be solved in any language; weird corner cases in the FFI cannot.

Hey, I did the same thing this year! At least I tried to do AoC in Rust until I reached the problem which required using doubly-linked lists... Turns out it's not quite as easy as a Rust newbie would think.

Regarding input parsing, I pretty quickly converged to using regexes and never looked back. I guess it's an acquired taste, but rubular.com is my best friend when it comes to it.

In my sibling post on performance I go into details on the doubly-linked list problem. https://www.forrestthewoods.com/blog/solving-advent-of-code-...

You're right that Rust doesn't make doubly-linked lists easy. For that problem I somewhat skirted the issue. For a later problem I built an Octree. I used an Rc<RefCell<OctreeNode>>. That feels pretty gross, but I'm still not sure what the idiomatic pattern is. :(

In practice, most data structures like that in Rust written to be efficient for real-world make judicious use of unsafe. For something like Advent of Code, `Rc<RefCell<_>` is probably the correct way to go.

The community is slowly standardising on using arena-like patterns for this kind of stuff

Thanks a lot for writing this up! A lot of good stuff to digest here.

I used this years AoC to learn rust and had similar issues with input parsing. I found a fairly simple parsing crate (https://docs.rs/scan-rules/0.2.0/scan_rules/) which helped a lot with the simpler inputs (day 24 was still a pain). It pretty much exactly matches the format you described.

I used AoC to try and learn a bit of Nim as I liked the idea of writting simple scripts that could run many times faster than Python. I somewhat regret it though.

I actually considered Rust given its popularity. I program C++ for a living, but I'd love to find some use for Rust.

Rust will probably my language of choice for this year's challenge.

what kind of regrets do you have?

I haven't used Nim for anything else since then. If I had to give a simplified critique, the language in itself is still springing to life and the user base and documentation are small.

I currently don't have the time to sink into something that could end up being a gimmick only, while there is some hardcore optimization needed in one of our services that could be approached through a better parallelization using Rust.

While from my point of view Nim is only interesting, Rust on the other hand seems promising. (I don't mean to compare different purpose languages, the problem stands on my spare time to study and the company needs, if that makes sense.)

Hey! Nim core dev here. I was going to offer my support when I saw your original comment but based on your critique so far there isn't that much that I can do, other than keep doing what I'm doing already (evangelizing Nim as much as I can).

This kind of thing does really worry me though, it's almost like a self-fulfilling prophecy, you aren't going to use Nim because you think there isn't enough people using it :(

Do you have any ideas of how we can make you and people like you change their mind?

Hey mate, awesome you messaged! I am sincerely not skilled enough to comment on the minutia of the language. I can say I really love the idea behind it and as far as I can tell the implementation is solid.

From a end user point of view, I thought the official website and documentation felt a bit lackluster, not because it was bad or anything (the Nim tutorials part I & II are a godsend), but I instinctively compared it to languages like Python. Again, this is not a fair comparison, more like a impression. I'd love to see more blog posts like this one [0] or maybe the occasional roadmap.

Ah I understand where the 'self-fulfilling prophecy' is coming, but if anything I talked about Nim to dozens of people already, even if I can't currently find utility for it on my day job (I do ML, Embedded and Image processing, so it is actually really hard to use anything other than c++)

Again, commenting from the outside, it seems it is a hard position because today upcoming languages such as rust and go have some solid enterprise backing.

If anything, I don't think you need to change my mind! The Nim development is something I keep a close eye on and I hope I will progressively incorporate it on my daily tasks. The mentioned regret on the first post has really more to do on how little spare time I have.

[0]: https://nim-lang.org/blog/2018/06/07/create-a-simple-macro.h...

I do appreciate the rule set that Rust forces the programmer into, however I keep coming back to it felt to me that I was being asked to conform to the language. Maybe I'm looking at it all wrong. When I was also learning other languages it felt to me some of them conformed to me more than others, and therefore were easier to write logic towards. Is low level safe systems programming the main/only use case or is there a reason to use Rust in other domains?

It is true, you have to conform to Rust. Rust looks like an imperative and OO language, but it isn't fully either of these, so some of your intuition you may have from other languages won't be applicable.

• Rust requires more rigor around ownership and borrowing. You can't juggle pointers as freely as you'd in C, or reference everything everywhere like in GC languages.

• Rust doesn't have inheritance, so many design patterns common in C++ or Java need rethinking in Rust.

If you really insist on doing things exactly the C way, or exactly the OOP way, then some things may work, some can be fudged, but it's going to be awkward.

However, if you adopt Rust's style, which is mostly functional, with clearly thought out ownership, without too many cyclic dependencies, then everything nicely falls into place, and Rust becomes easy and productive.

Rust is a bit like Haskell: a very strong but also complicated type system that enables you to encode a lot of the logical restrictions into the API.

This is most notable immediately around the Result and Option types with their many modifiers (map, map_err, and_then, ...) and how strings work.

This can feel pretty awkward and cumbersome compared to other languages and that could be what you are describing.

It's not that way just for fun though: Most languages hide the underlying complexity from you and 'punish' you at runtime if you violate invariants by eg throwing exceptions. Rust forces you to deal with the invariants at compile time.

This makes you write safer code and prevents many errors, even "business logic" type errors if the API is right.

This is why "move fast and break things" aka prototyping is hard with Rust. (Note you can also circumvent many of these restrictions by using "unwrap()" et al a lot)

On the other hand, it gives you a high degree of confidence for writing and refactoring code: if it compiles, it probably works. (Assuming you were writing idiomatic code)

Rust is certainly not a good fit for every domain: the manual memory management is often overkill, and in very business logic heavy code like your typical CRUD apps, where most errors happen in logic that can't really be encoded in the type system, the value is limited.

“If it compiles, it probably works.”

This has been so true in my experience. I find that it does take longer overall to write something in Rust, but I almost never have to root out strange errors from my code. In shorter files, I almost never have to use ‘cargo run --debug’ twice.

I second that! I wrote a small service for a friend the other day. It had to read some bytes off a tcp stream in a certain format and route them onto an obscure protocol called a modbus. Given a screenshot of some sample input data from an excel spreadsheet and some other minimal information about the target destination format and address I was able to make the service without having access to a real input tcp stream or the destination hardware required for the modbus. However, to our amazement the software ran without fault the first time it was executed on the target server. Sure I struggled to compile it because I am still a beginner but that has just never happened to me before in my professional career with c#. There is always some side case I haven’t thought about no matter how many unit tests I write! Maybe I got lucky but it definitely had nothing to do with my skills with rust which are still basic.

To add a bit to what others have said, at some point, once you internalize what Rust asks of you, it no longer feels like conformance; you just write stuff in a Rust-y way, and things get a lot easier and more fluid. This is sort of true in all languages, ("you can write Java in any language"), but Rust doesn't let you get away with writing some types of code as much as other languages do.

Like many things, it's a tradeoff: is the cost of getting up to speed worth the benefits Rust gives? Like any tradeoff, some will say yes, some will say no.

What is HN-users view of learning a programming language through this kind of problem-solving?

Is it limiting?

Should this be used as a complement to a larger toy project?

It depends on the things that you want to learn and amount of extra work you want to put into it. For instance, I used Go for the most of AoC 2018 problems and I had always been using C/C++ in algorithmic contests when execution time mattered. And yet, I wouldn't say I know any of these languages good enough to write a production-grade system because I only learned usage patterns common for small, write - run once - throw away applications.

On the other hand, the author of that post seems to have used a lot of more complex language features, did profiling, debugging, made use of many libraries etc - a lot of extra stuff that wasn't necessary to give correct answers but is crucial in 'real world' programming.

A long, long time ago, I used Haskell to reimplement the examples from Software Tools (https://crsr.net/Programming_Languages/SoftwareTools/). It was how I got over the How-do-I-write-real-code-in-Haskell hump.

I strongly recommend it.

I've been starting to learn Rust and found the post quite useful. I think the inherit constraints with these sorts of challenges allow for time to think of creative ways to use a language's fundamentals, rather than focusing too much on architecture. It forces learning through immersion, akin to being dropped in a foreign land without knowing the language.

It depends on your style of learning tbh. I enjoy going through a language's basics in docs (Golang playground and Rust's docs are fantastic) and then solving a problem.

My first "app" in Golang was a discord bot. I have to do maintenance on it occasionally and it's been a great learning experience.

I would say it depends on what you are trying to learn.

These types of problems will give you a good intro to a language, but won't introduce you to the more advanced libraries which you'll likely need in a professional context (e.g. threading/mutexs, tcp/udp, etc).

I think this kind of learning is great. One of the benefits of this type of exercise is that tackle certain types of problems that are not that common in day-to-day coding, and you do it over and over, so you become quite comfortable with areas that you might otherwise not use frequently.

Building larger projects is of course also a great way to learn!

I lean towards keeping to one kind of challenge at a time. If the problems wouldn't be hard in your favorite language, mostly, then this is a great way to exercise a new one. It's fun and you can learn more by checking other people's solutions afterwards.

I haven't tried rust in a few years, but I remember it was kind of annoying that you couldn't tell from a function name which parameters would invoke borrow checker stuff. At that time there wasn't great IDE support, but is there a common/idiomatic way of hinting the borrowing of function parameters?

documentation is much better, here are a few links that should answer your question

https://doc.rust-lang.org/book/ch04-02-references-and-borrow... https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html

I used AoC to brush up on my python skills. Which was quite good - as soon I am participating in a programming competition and my team is using Python.

In general AoC is just great for exploring new langs or brushing up on ones you haven't used for some time.

FWIW perf in Linux has rust symbol demangling.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact