blog/assets/js/51624505.60fbe3b9.js
github-actions[bot] 0b9bf3d392 deploy: dadb0d51f7
2023-11-28 18:40:59 +00:00

1 line
No EOL
21 KiB
JavaScript

"use strict";(self.webpackChunkfi=self.webpackChunkfi||[]).push([[4394],{32609:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>a,default:()=>h,frontMatter:()=>i,metadata:()=>s,toc:()=>c});var r=t(85893),o=t(11151);const i={title:"Advent of Code '22 in Rust",description:"Preparing for Advent of Code '22.",date:"2022-12-14T21:45",slug:"aoc-2022/intro",authors:"mf",tags:["advent-of-code","advent-of-code-2022","rust"],hide_table_of_contents:!1},a=void 0,s={permalink:"/blog/aoc-2022/intro",editUrl:"https://github.com/mfocko/blog/tree/main/blog/aoc-2022/00-intro.md",source:"@site/blog/aoc-2022/00-intro.md",title:"Advent of Code '22 in Rust",description:"Preparing for Advent of Code '22.",date:"2022-12-14T21:45:00.000Z",formattedDate:"December 14, 2022",tags:[{label:"advent-of-code",permalink:"/blog/tags/advent-of-code"},{label:"advent-of-code-2022",permalink:"/blog/tags/advent-of-code-2022"},{label:"rust",permalink:"/blog/tags/rust"}],readingTime:8.665,hasTruncateMarker:!0,authors:[{name:"Matej Focko",email:"me+blog@mfocko.xyz",title:"a.k.a. @mf",url:"https://gitlab.com/mfocko",imageURL:"https://github.com/mfocko.png",key:"mf"}],frontMatter:{title:"Advent of Code '22 in Rust",description:"Preparing for Advent of Code '22.",date:"2022-12-14T21:45",slug:"aoc-2022/intro",authors:"mf",tags:["advent-of-code","advent-of-code-2022","rust"],hide_table_of_contents:!1},unlisted:!1,prevItem:{title:"1st week of Advent of Code '22 in Rust",permalink:"/blog/aoc-2022/1st-week"}},l={authorsImageUrls:[void 0]},c=[{value:"Choosing a language",id:"choosing-a-language",level:2},{value:"Choosing libraries",id:"choosing-libraries",level:2},{value:"Preparations for Rust",id:"preparations-for-rust",level:2},{value:"Toolkit",id:"toolkit",level:3},{value:"Libraries",id:"libraries",level:3},{value:"My own \u201clibrary\u201d",id:"my-own-library",level:3},{value:"Skeleton",id:"skeleton",level:3}];function d(e){const n={a:"a",admonition:"admonition",code:"code",em:"em",h2:"h2",h3:"h3",li:"li",ol:"ol",p:"p",pre:"pre",section:"section",strong:"strong",sup:"sup",...(0,o.a)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)(n.p,{children:["Let's talk about the preparations for this year's ",(0,r.jsx)(n.a,{href:"https://adventofcode.com",children:(0,r.jsx)(n.em,{children:"Advent of Code"})}),"."]}),"\n",(0,r.jsx)(n.h2,{id:"choosing-a-language",children:"Choosing a language"}),"\n",(0,r.jsx)(n.p,{children:"When choosing a language for AoC, you usually want a language that gives you a\nquick feedback which allows you to iterate quickly to the solution of the puzzle.\nOne of the most common choices is Python, many people also use JavaScript or Ruby."}),"\n",(0,r.jsx)(n.p,{children:"Given the competitive nature of the AoC and popularity among competitive programming,\nC++ might be also a very good choice. Only if you are familiar with it, I guess\u2026"}),"\n",(0,r.jsx)(n.p,{children:"If you want a challenge, you might also choose to rotate the languages each day.\nThough I prefer to use only one language."}),"\n",(0,r.jsxs)(n.p,{children:["For this year I have been deciding between ",(0,r.jsx)(n.em,{children:"Rust"}),", ",(0,r.jsx)(n.em,{children:"C++"})," and ",(0,r.jsx)(n.em,{children:"Pascal"})," or ",(0,r.jsx)(n.em,{children:"Ada"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["I have tried Rust last year and have survived with it for 3 days and then gave\nup and switched to ",(0,r.jsx)(n.em,{children:"Kotlin"}),", which was pretty good given it is \u201cJava undercover\u201d.\nI pretty much like the ideas behind Rust, I am not sure about the whole cult and\nimplementation of those ideas though. After some years with C/C++, I would say\nthat Rust feels ",(0,r.jsx)(n.em,{children:"too safe"})," for my taste and tries to \u201c",(0,r.jsx)(n.em,{children:"punish me"}),"\u201d even for the\nmost trivial things."]}),"\n",(0,r.jsxs)(n.p,{children:["C++ is a very robust, but also comes with a wide variety of options providing you\nthe ability to shoot yourself in the leg. I have tried to solve few days of previous\nAdvent of Code events, it was ",(0,r.jsx)(n.em,{children:"relatively easy"})," to solve the problems in C++, given\nthat I do not admit writing my own iterator for ",(0,r.jsx)(n.code,{children:"enumerate"}),"\u2026"]}),"\n",(0,r.jsx)(n.p,{children:"Pascal or Ada were meme choices :) Ada is heavily inspired by Pascal and has a\npretty nice standard library that offers enough to be able to quickly solve some\nproblems in it. However the toolkit is questionable :/"}),"\n",(0,r.jsx)(n.h2,{id:"choosing-libraries",children:"Choosing libraries"}),"\n",(0,r.jsx)(n.h2,{id:"preparations-for-rust",children:"Preparations for Rust"}),"\n",(0,r.jsxs)(n.p,{children:["All of the sources, later on including solutions, can be found at my\n",(0,r.jsx)(n.a,{href:"https://gitlab.com/mfocko/advent-of-code-2022",children:"GitLab"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"toolkit",children:"Toolkit"}),"\n",(0,r.jsxs)(n.p,{children:["Since we are using Rust, we are going to use a ",(0,r.jsx)(n.a,{href:"https://doc.rust-lang.org/cargo/",children:"Cargo"})," and more than likely VSCode\nwith ",(0,r.jsx)(n.a,{href:"https://rust-analyzer.github.io/",children:(0,r.jsx)(n.code,{children:"rust-analyzer"})}),". Because of my choice of libraries, we will also introduce\na ",(0,r.jsx)(n.code,{children:".envrc"})," file that can be used by ",(0,r.jsx)(n.a,{href:"https://direnv.net/",children:(0,r.jsx)(n.code,{children:"direnv"})}),", which allows you to set specific\nenvironment variables when you enter a directory. In our case, we will use"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"# to show nice backtrace when using the color-eyre\nexport RUST_BACKTRACE=1\n\n# to catch logs generated by tracing\nexport RUST_LOG=trace\n"})}),"\n",(0,r.jsxs)(n.p,{children:["And for the one of the most obnoxious things ever, we will use a script to download\nthe inputs instead of \u201c",(0,r.jsx)(n.em,{children:"clicking, opening and copying to a file"}),"\u201d",(0,r.jsx)(n.sup,{children:(0,r.jsx)(n.a,{href:"#user-content-fn-1-793a30",id:"user-content-fnref-1-793a30","data-footnote-ref":!0,"aria-describedby":"footnote-label",children:"1"})}),". There is\nno need to be ",(0,r.jsx)(n.em,{children:"fancy"}),", so we will adjust Python script by Martin",(0,r.jsx)(n.sup,{children:(0,r.jsx)(n.a,{href:"#user-content-fn-2-793a30",id:"user-content-fnref-2-793a30","data-footnote-ref":!0,"aria-describedby":"footnote-label",children:"2"})}),"."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-py",children:'#!/usr/bin/env python3\n\nimport datetime\nimport yaml\nimport requests\nimport sys\n\n\ndef load_config():\n with open("env.yaml", "r") as f:\n js = yaml.load(f, Loader=yaml.Loader)\n return js["session"], js["year"]\n\n\ndef get_input(session, year, day):\n return requests.get(\n f"https://adventofcode.com/{year}/day/{day}/input",\n cookies={"session": session},\n headers={\n "User-Agent": "{repo} by {mail}".format(\n repo="gitlab.com/mfocko/advent-of-code-2022",\n mail="me@mfocko.xyz",\n )\n },\n ).content.decode("utf-8")\n\n\ndef main():\n day = datetime.datetime.now().day\n if len(sys.argv) == 2:\n day = sys.argv[1]\n\n session, year = load_config()\n problem_input = get_input(session, year, day)\n\n with open(f"./inputs/day{day:>02}.txt", "w") as f:\n f.write(problem_input)\n\n\nif __name__ == "__main__":\n main()\n'})}),"\n",(0,r.jsx)(n.p,{children:"If the script is called without any arguments, it will deduce the day from the\nsystem, so we do not need to change the day every morning. It also requires a\nconfiguration file:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-yaml",children:"# env.yaml\nsession: \u2039your session cookie\u203a\nyear: 2022\n"})}),"\n",(0,r.jsx)(n.h3,{id:"libraries",children:"Libraries"}),"\n",(0,r.jsx)(n.p,{children:"Looking at the list of the libraries, I have chosen \u201ca lot\u201d of them. Let's walk\nthrough each of them."}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://crates.io/crates/tracing",children:(0,r.jsx)(n.code,{children:"tracing"})})," and ",(0,r.jsx)(n.a,{href:"https://crates.io/crates/tracing-subscriber",children:(0,r.jsx)(n.code,{children:"tracing-subscriber"})})," are the crates that can be used for tracing\nand logging of your Rust programs, there are also other crates that can help you\nwith providing backtrace to the Sentry in case you have deployed your application\nsomewhere and you want to watch over it. In our use case we will just utilize the\nmacros for debugging in the terminal."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://crates.io/crates/thiserror",children:(0,r.jsx)(n.code,{children:"thiserror"})}),", ",(0,r.jsx)(n.a,{href:"https://crates.io/crates/anyhow",children:(0,r.jsx)(n.code,{children:"anyhow"})})," and ",(0,r.jsx)(n.a,{href:"https://crates.io/crates/color-eyre",children:(0,r.jsx)(n.code,{children:"color-eyre"})})," are used for error reporting.\n",(0,r.jsx)(n.code,{children:"thiserror"})," is a very good choice for libraries, cause it extends the ",(0,r.jsx)(n.code,{children:"Error"}),"\nfrom the ",(0,r.jsx)(n.code,{children:"std"})," and allows you to create more convenient error types. Next is\n",(0,r.jsx)(n.code,{children:"anyhow"})," which kinda builds on top of the ",(0,r.jsx)(n.code,{children:"thiserror"})," and provides you with simpler\nerror handling in binaries",(0,r.jsx)(n.sup,{children:(0,r.jsx)(n.a,{href:"#user-content-fn-3-793a30",id:"user-content-fnref-3-793a30","data-footnote-ref":!0,"aria-describedby":"footnote-label",children:"3"})}),". And finally we have ",(0,r.jsx)(n.code,{children:"color-eyre"})," which, as I found\nout later, is a colorful (",(0,r.jsx)(n.em,{children:"wink wink"}),") extension of ",(0,r.jsx)(n.code,{children:"eyre"})," which is fork of ",(0,r.jsx)(n.code,{children:"anyhow"}),"\nwhile supporting customized reports."]}),"\n",(0,r.jsxs)(n.p,{children:["In the end I have decided to remove ",(0,r.jsx)(n.code,{children:"thiserror"})," and ",(0,r.jsx)(n.code,{children:"anyhow"}),", since first one is\nsuitable for libraries and the latter was basically fully replaced by ",(0,r.jsx)(n.code,{children:"{color-,}eyre"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://crates.io/crates/regex",children:(0,r.jsx)(n.code,{children:"regex"})})," and ",(0,r.jsx)(n.a,{href:"https://crates.io/crates/lazy_static",children:(0,r.jsx)(n.code,{children:"lazy_static"})})," are a very good and also, I hope, self-explanatory\ncombination. ",(0,r.jsx)(n.code,{children:"lazy_static"})," allows you to have static variables that must be initialized\nduring runtime."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://crates.io/crates/itertools",children:(0,r.jsx)(n.code,{children:"itertools"})})," provides some nice extensions to the iterators from the ",(0,r.jsx)(n.code,{children:"std"}),"."]}),"\n",(0,r.jsx)(n.h3,{id:"my-own-library",children:"My own \u201clibrary\u201d"}),"\n",(0,r.jsxs)(n.p,{children:["When creating the crate for this year's Advent of Code, I have chosen a library\ntype. Even though standard library is huge, some things might not be included and\nalso we can follow ",(0,r.jsx)(n.em,{children:"KISS"}),". I have 2 modules that my \u201clibrary\u201d exports, one for\nparsing and one for 2D vector (that gets used quite often during Advent of Code)."]}),"\n",(0,r.jsx)(n.p,{children:"Key part is, of course, processing the input and my library exports following\nfunctions that get used a lot:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",children:"/// Reads file to the string.\npub fn file_to_string<P: AsRef<Path>>(pathname: P) -> String;\n\n/// Reads file and returns it as a vector of characters.\npub fn file_to_chars<P: AsRef<Path>>(pathname: P) -> Vec<char>;\n\n/// Reads file and returns a vector of parsed structures. Expects each structure\n/// on its own line in the file. And `T` needs to implement `FromStr` trait.\npub fn file_to_structs<P: AsRef<Path>, T: FromStr>(pathname: P) -> Vec<T>\nwhere\n <T as FromStr>::Err: Debug;\n\n/// Converts iterator over strings to a vector of parsed structures. `T` needs\n/// to implement `FromStr` trait and its error must derive `Debug` trait.\npub fn strings_to_structs<T: FromStr, U>(\n iter: impl Iterator<Item = U>\n) -> Vec<T>\nwhere\n <T as std::str::FromStr>::Err: std::fmt::Debug,\n U: Deref<Target = str>;\n\n/// Reads file and returns it as a vector of its lines.\npub fn file_to_lines<P: AsRef<Path>>(pathname: P) -> Vec<String>;\n"})}),"\n",(0,r.jsxs)(n.p,{children:["As for the vector, I went with a rather simple implementation that allows only\naddition of the vectors for now and accessing the elements via functions ",(0,r.jsx)(n.code,{children:"x()"}),"\nand ",(0,r.jsx)(n.code,{children:"y()"}),". Also the vector is generic, so we can use it with any numeric type we\nneed."]}),"\n",(0,r.jsx)(n.h3,{id:"skeleton",children:"Skeleton"}),"\n",(0,r.jsx)(n.p,{children:"We can also prepare a template to quickly bootstrap each of the days. We know\nthat each puzzle has 2 parts, which means that we can start with 2 functions that\nwill solve them."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",children:"fn part1(input: &Input) -> Output {\n todo!()\n}\n\nfn part2(input: &Input) -> Output {\n todo!()\n}\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Both functions take reference to the input and return some output (in majority\nof puzzles, it is the same type). ",(0,r.jsx)(n.code,{children:"todo!()"})," can be used as a nice placeholder,\nit also causes a panic when reached and we could also provide some string with\nan explanation, e.g. ",(0,r.jsx)(n.code,{children:'todo!("part 1")'}),". We have not given functions a specific\ntype and to avoid as much copy-paste as possible, we will introduce type aliases."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",children:"type Input = String;\ntype Output = i32;\n"})}),"\n",(0,r.jsx)(n.admonition,{type:"tip",children:(0,r.jsxs)(n.p,{children:["This allows us to quickly adjust the types only in one place without the need to\ndo ",(0,r.jsx)(n.em,{children:"regex-replace"})," or replace them manually."]})}),"\n",(0,r.jsx)(n.p,{children:"For each day we get a personalized input that is provided as a text file. Almost\nall the time, we would like to get some structured type out of that input, and\ntherefore it makes sense to introduce a new function that will provide the parsing\nof the input."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",children:"fn parse_input(path: &str) -> Input {\n todo!()\n}\n"})}),"\n",(0,r.jsx)(n.p,{children:"This \u201cparser\u201d will take a path to the file, just in case we would like to run the\nsample instead of input."}),"\n",(0,r.jsxs)(n.p,{children:["OK, so now we can write a ",(0,r.jsx)(n.code,{children:"main"})," function that will take all of the pieces and\nrun them."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",children:'fn main() {\n let input = parse_input("inputs/dayXX.txt");\n\n println!("Part 1: {}", part_1(&input));\n println!("Part 2: {}", part_2(&input));\n}\n'})}),"\n",(0,r.jsxs)(n.p,{children:["This would definitely do :) But we have installed a few libraries and we want to\nuse them. In this part we are going to utilize ",(0,r.jsx)(n.em,{children:(0,r.jsx)(n.a,{href:"https://crates.io/crates/tracing",children:(0,r.jsx)(n.code,{children:"tracing"})})})," (for tracing, duh\u2026)\nand ",(0,r.jsx)(n.em,{children:(0,r.jsx)(n.a,{href:"https://crates.io/crates/color-eyre",children:(0,r.jsx)(n.code,{children:"color-eyre"})})})," (for better error reporting, e.g. from parsing)."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",children:'fn main() -> Result<()> {\n tracing_subscriber::fmt()\n .with_env_filter(EnvFilter::from_default_env())\n .with_target(false)\n .with_file(true)\n .with_line_number(true)\n .without_time()\n .compact()\n .init();\n color_eyre::install()?;\n\n let input = parse_input("inputs/dayXX.txt");\n\n info!("Part 1: {}", part_1(&input));\n info!("Part 2: {}", part_2(&input));\n\n Ok(())\n}\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The first statement will set up tracing and configure it to print out the logs to\nterminal, based on the environment variable. We also change the formatting a bit,\nsince we do not need all the ",(0,r.jsx)(n.em,{children:"fancy"})," features of the logger. Pure initialization\nwould get us logs like this:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"2022-12-11T19:53:19.975343Z INFO day01: Part 1: 0\n"})}),"\n",(0,r.jsx)(n.p,{children:"However after running that command, we will get the following:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:" INFO src/bin/day01.rs:35: Part 1: 0\n"})}),"\n",(0,r.jsxs)(n.p,{children:["And the ",(0,r.jsx)(n.code,{children:"color_eyre::install()?"})," is quite straightforward. We just initialize the\nerror reporting by ",(0,r.jsx)(n.em,{children:"color eyre"}),"."]}),"\n",(0,r.jsx)(n.admonition,{type:"caution",children:(0,r.jsxs)(n.p,{children:["Notice that we had to add ",(0,r.jsx)(n.code,{children:"Ok(())"})," to the end of the function and adjust the\nreturn type of the ",(0,r.jsx)(n.code,{children:"main"})," to ",(0,r.jsx)(n.code,{children:"Result<()>"}),". It is caused by the ",(0,r.jsx)(n.em,{children:"color eyre"})," that\ncan be installed only once and therefore it can fail, that is how we got the ",(0,r.jsx)(n.code,{children:"?"}),"\nat the end of the ",(0,r.jsx)(n.code,{children:"::install"})," which ",(0,r.jsx)(n.em,{children:"unwraps"})," the ",(0,r.jsx)(n.strong,{children:"\xbbresult\xab"})," of the installation."]})}),"\n",(0,r.jsx)(n.p,{children:"Overall we will get to a template like this:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-rust",children:'use aoc_2022::*;\n\nuse color_eyre::eyre::Result;\nuse tracing::info;\nuse tracing_subscriber::EnvFilter;\n\ntype Input = String;\ntype Output = i32;\n\nfn parse_input(path: &str) -> Input {\n todo!()\n}\n\nfn part1(input: &Input) -> Output {\n todo!()\n}\n\nfn part2(input: &Input) -> Output {\n todo!()\n}\n\nfn main() -> Result<()> {\n tracing_subscriber::fmt()\n .with_env_filter(EnvFilter::from_default_env())\n .with_target(false)\n .with_file(true)\n .with_line_number(true)\n .without_time()\n .compact()\n .init();\n color_eyre::install()?;\n\n let input = parse_input("inputs/dayXX.txt");\n\n info!("Part 1: {}", part_1(&input));\n info!("Part 2: {}", part_2(&input));\n\n Ok(())\n}\n'})}),"\n",(0,r.jsxs)(n.section,{"data-footnotes":!0,className:"footnotes",children:[(0,r.jsx)(n.h2,{className:"sr-only",id:"footnote-label",children:"Footnotes"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{id:"user-content-fn-1-793a30",children:["\n",(0,r.jsxs)(n.p,{children:["Copy-pasting might be a relaxing thing to do, but you can also discover\nnasty stuff about your PC. See ",(0,r.jsx)(n.a,{href:"https://www.reddit.com/r/adventofcode/comments/zb98pn/comment/iyq0ono",children:"this Reddit post and the comment"}),". ",(0,r.jsx)(n.a,{href:"#user-content-fnref-1-793a30","data-footnote-backref":"","aria-label":"Back to reference 1",className:"data-footnote-backref",children:"\u21a9"})]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{id:"user-content-fn-2-793a30",children:["\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/martinjonas",children:"GitHub profile"})," ",(0,r.jsx)(n.a,{href:"#user-content-fnref-2-793a30","data-footnote-backref":"","aria-label":"Back to reference 2",className:"data-footnote-backref",children:"\u21a9"})]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{id:"user-content-fn-3-793a30",children:["\n",(0,r.jsxs)(n.p,{children:["Even though you can use it even for libraries, but handling errors from\nlibraries using ",(0,r.jsx)(n.code,{children:"anyhow"})," is nasty\u2026 You will be the stinky one ;) ",(0,r.jsx)(n.a,{href:"#user-content-fnref-3-793a30","data-footnote-backref":"","aria-label":"Back to reference 3",className:"data-footnote-backref",children:"\u21a9"})]}),"\n"]}),"\n"]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,o.a)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},11151:(e,n,t)=>{t.d(n,{Z:()=>s,a:()=>a});var r=t(67294);const o={},i=r.createContext(o);function a(e){const n=r.useContext(i);return r.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function s(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:a(e.components),r.createElement(i.Provider,{value:n},e.children)}}}]);