9 ms·
Learning Rust via Advent of Code
- hocuspocus 8y agoI'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.
- K0nserv 8y agoAgreed. Most of my solutions[0] use something simple like input .lines() .map(|s| s.trim()) .filter(|l| l.len() > 0) .map(CustomType::from) .collect::<Vec<_>>(); or for grid-like puzzels input .lines() .map(|s| s.trim()) .filter(|l| l.len() > 0) .map(|l| l .chars() .map(CustomType::from) .collect::<Vec<_>>() ) .collect::<Vec<_>>(); 0: https://github.com/k0nserv/advent-of-rust-2018 https://github.com/k0nserv/advent-of-rust-2018
- initium 8y agoWhat 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?
- jihadjihad 8y agoI'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.
- jgforbes 8y agoI 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).
- didymospl 8y agoIt 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.
- antjanus 8y agoIt 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.
- ragona 8y agoI 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!
- mcguire 8y agoA long, long time ago, I used Haskell to reimplement the examples from Software Tools (https://crsr.net/Programming_Languages/SoftwareTools/ 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.
- abecedarius 8y agoI 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.
- masklinn 8y agoSome 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. std::cmp::Reverse > 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); sort_by_key? > 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.
- floofy222 8y agoThe #[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);
- tlent 8y agoAs 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 https://github.com/rust-lang-nursery/lazy-static.rs#example
- steveklabnik 8y agoThanks a lot for writing this up! A lot of good stuff to digest here.
- robostac 8y agoI 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/ 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.
- wtmt 8y ago@forrestthewoods You have an incorrect link under the "Advent of Code" section. The first link reads https://www.forrestthewoods.com/blog/learning-rust-via-advent-of-code/(https://adventofcode.com/ It should just be https://adventofcode.com/ https://adventofcode.com/
- forrestthewoods 8y agoFixed!
- thekingofh 8y agoI 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?
- the_duke 8y agoRust 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.
- justwalt 8y ago“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.
- tpavel 8y agoHey, 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.
- forrestthewoods 8y agoIn my sibling post on performance I go into details on the doubly-linked list problem. https://www.forrestthewoods.com/blog/solving-advent-of-code-in-under-a-second/ 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. :(
- saghm 8y agoIn 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.
- Ar-Curunir 8y agoThe community is slowly standardising on using arena-like patterns for this kind of stuff
- Dowwie 8y agoIf 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. https://github.com/BurntSushi/advent-of-code https://github.com/BurntSushi/advent-of-code You develop Rust skills by writing your own and then hone your skills by learning from idiomatic Rust.
- reificator 8y agoI learned Go first and then went to Rust, which was convenient because it appears sushi went the same route.
- Dowwie 8y agoI suspect his current route includes both Rust and Go. :)
- reificator 8y agoSame with mine.
- deleted 8y ago[deleted]
- dilap 8y agoFellow Gopher here interested in learning Rust. Any recommendations?
- mcguire 8y ago"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.)
- masklinn 8y ago> 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, …
- Twirrim 8y agoI'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.
- steveklabnik 8y agoPart 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.
- TotempaaltJ 8y agoSo then why isn't the regex lib part of the standard library?
- nbeleski 8y agoI 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.
- hnbroseph 8y agowhat kind of regrets do you have?
- nbeleski 8y agoI 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.)
- dom96 8y agoHey! 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?
- nbeleski 8y agoHey 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.html https://nim-lang.org/blog/2018/06/07/create-a-simple-macro.h...
- k3lsi3r 8y agoThis 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?
- w4tson 8y agohttps://github.com/Bogdanp/awesome-advent-of-code https://github.com/Bogdanp/awesome-advent-of-code 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.
- WakiMiko 8y agoFor 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| { a.year.cmp(&b.year) .then(a.month.cmp(&b.month)) .then(a.day.cmp(&b.day)) }); 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, }
- masklinn 8y agoOr 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.
- WakiMiko 8y agoI quite like this tuple approach. And yeah, I meant Ord (and the required other Traits), I edited my post. Thanks for pointing this out.
- Insanity 8y agoI 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.
- erentz 8y agoSlightly 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?
- preordained 8y agocodingbat.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
- general_pizza 8y agoI 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.split https://doc.rust-lang.org/std/primitive.str.html#method.spli...
- terhechte 8y agoThis 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.
- gridspy 8y agoVec<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. https://doc.rust-lang.org/book/ch04-03-slices.html#string-slices https://doc.rust-lang.org/book/ch04-03-slices.html#string-sl... https://doc.rust-lang.org/std/slice/ https://doc.rust-lang.org/std/slice/
- steveklabnik 8y ago&str is not the same as &[char]. &str has the same memory representation as &[u8]. char is four bytes, not one.
- 8y ago
- dtolnay 8y ago> 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. https://crates.io/crates/text_io https://crates.io/crates/text_io #[macro_use] 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); }
- nicoburns 8y agoOhhh, now that's super handy.
- nixpulvis 8y agoI wonder if we'll ever see `scan!` in the STD lib...
- josephg 8y agoI 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!
- saghm 8y agoI 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.
- gmfawcett 8y agoI 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.
- Teknoman117 8y agoFWIW perf in Linux has rust symbol demangling.
- nprateem 8y agoI 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?
- Hortinstein 8y agodocumentation is much better, here are a few links that should answer your question https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html https://doc.rust-lang.org/book/ch04-02-references-and-borrow... https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html
- jeffdavis 8y agoI 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.