From 7ee9e8cb8e1e63af0fe5e10c7a1486c113964851 Mon Sep 17 00:00:00 2001 From: CherryKitten Date: Thu, 23 Feb 2023 17:33:46 +0100 Subject: [PATCH] New blogpost! (and some updates) --- config.toml | 8 +- content/blog/rust_1_options_results.md | 245 +++++++++++++ content/{index.md => home.md} | 1 + public/404.html | 7 +- public/about/index.html | 7 +- .../fediverse-isnt-just-mastodon/index.html | 23 +- public/blog/index.html | 60 +++- public/blog/rust-1-options-results/index.html | 324 ++++++++++++++++++ public/buttons.css | 2 +- public/color/background_blue.css | 2 +- public/color/background_dark.css | 2 +- public/color/background_green.css | 2 +- public/color/background_orange.css | 2 +- public/color/background_pink.css | 2 +- public/color/background_red.css | 2 +- public/color/background_rosepine.css | 2 +- public/color/blue.css | 2 +- public/color/green.css | 2 +- public/color/orange.css | 2 +- public/color/pink.css | 2 +- public/color/red.css | 2 +- public/color/rosepine.css | 2 +- public/contact/index.html | 7 +- public/font-hack-subset.css | 2 +- public/font-hack.css | 2 +- public/footer.css | 2 +- public/header.css | 2 +- public/{content => home}/index.html | 9 +- public/impressum/index.html | 7 +- public/index.html | 9 +- public/logo.css | 2 +- public/main.css | 2 +- public/pagination.css | 2 +- public/post.css | 2 +- public/rss.xml | 53 ++- public/sitemap.xml | 18 +- public/style.css | 2 +- 37 files changed, 776 insertions(+), 48 deletions(-) create mode 100644 content/blog/rust_1_options_results.md rename content/{index.md => home.md} (95%) create mode 100644 public/blog/rust-1-options-results/index.html rename public/{content => home}/index.html (93%) diff --git a/config.toml b/config.toml index 4de919d..c48ed22 100644 --- a/config.toml +++ b/config.toml @@ -39,7 +39,12 @@ background_color = "rosepine" logo_text = "CherryKitten" copyright_html = """\ -

© {currentYear} - CherryKitten

Impressum +

© 2023 - CherryKitten


+

Impressum

+   +

RSS

+   + 🐱 """ menu_items = [ @@ -53,3 +58,4 @@ menu_items = [ ] page_titles = "combined" +post_view_navigation_prompt = "More posts!" diff --git a/content/blog/rust_1_options_results.md b/content/blog/rust_1_options_results.md new file mode 100644 index 0000000..29c1377 --- /dev/null +++ b/content/blog/rust_1_options_results.md @@ -0,0 +1,245 @@ ++++ +title = "Learning Rust Part 1: A kitten's guide to Options and Results" +date = 2023-02-25 +[taxonomies] +tags=["Rust", "programming", "code", "learning"] ++++ +### To unwrap() or not to unwrap(), that is the question: + +So I've finally given in and started to learn Rust last month. It's a really cool programming language, +with some interesting differences to what I've used before. (JavaScript and Python, mostly) + +There are some really pawesome guides out there, ["The Rust programming language"](https://doc.rust-lang.org/book/) is +definitely a **must-read** in my opinion, and [Rustlings](https://github.com/rust-lang/rustlings) is nyamazing for +anyone who likes to learn by actively working through interactive problems. + +After reading through a lot of those big thorough guides by experienced Rust developers, I've started working on +my first actual Project. I approached the development of this project by just trying to get small parts of it +working in any way I can manage, and then build upon this. In that process, I learned a lot of small subtilties that +guides like the ones named above just can't really cover. This post is for sharing those things, those cool little +tips to make your first Rust project just a little cleaner and more Rust-y. Originally I wanted to make this about a lot +of different topics, but then I've realized that my notes already contain so many things about just one part of Rust: +The Enums `Option` and `Result`. So this post will be about those, and hopefully will mark the start of a series on this blog. + +While reading through this, you might think that the things I'm mentioning are obvious. That's okay, and that's the point. +Nothing is ever completely obvious to everyone, and this is for those like me, who often don't immediately recognize +the "obvious". And, to be honest, I am writing this just as much for myself, writing all of that stuff down to aid me in my +own ongoing learning process. + +So, let's start! + + + +Firstly, a very quick introduction. `Option` and `Result` are part of the Rust standard library. Quoting the official documentation +is probably the easiest way to summarize their purpose: + +> Type `Option` represents an optional value: every Option is either `Some` and contains a value, or `None`, and does not. `Option` types are very common in Rust code, as they have a number of uses: +> +> * Initial values +> * Return values for functions that are not defined over their entire input range (partial functions) +> * Return value for otherwise reporting simple errors, where `None` is returned on error +> * Optional struct fields +> * Struct fields that can be loaned or “taken” +> * Optional function arguments +> * Nullable pointers +> * Swapping things out of difficult situations + +and + +> `Result` is the type used for returning and propagating errors. It is an enum with the variants, `Ok(T)`, representing success and containing a value, and `Err(E)`, representing error and containing an error value. + +At first, it seems so easy to just add a quick `.unwrap()` after every `Option` or `Result`, but this comes with +the disadvantage of your code [panicking](https://doc.rust-lang.org/std/macro.panic.html) if it fails to unwrap. +Sometimes, this can be useful during development, to discover potential error cases you might not have thought about, but +is usually not what you want to happen. + +So, what can you do instead? + +First of all, don't use `unwrap()` unless you are completely sure that the value will never panic. Sometimes that is the +case, because an earlier part of your code already made sure that it is `Ok` or `Some`. + +In some cases, you actually want the program to panic. But even then, there is a slightly better way. You can use `expect("foo")` +to add a message to the panic, so the user actually knows what went wrong. That message should be worded in a specific way, +basically telling the user what you _expected_ to happen. + +```rust +fn main() { + let x = Some("Hello, World"); + // There is no actual "None" type/value in Rust, + // this "None" is more specifically Option::None + let y: Option = None; + + let z = x.unwrap(); // We explicitly set this to Some, so we can safely unwrap it + let rip = y.expect("expected y to not be None"); + + println!("{}, {} will never print because it panics above", z, rip); +} +``` + +There are also the non-panicking siblings of `unwrap()`, like `unwrap_or()`, `unwrap_or_else()` and `unwrap_or_default()`. + +```rust +fn main() { + let a: Option = None; + let b: Option = None; + let c: Option = None; + + // unwrap_or() lets you supply a specific value to use if the Option is None + let x = a.unwrap_or("Hello there".to_owned()); + + // unwrap_or_default() uses the types default value if the Option is None + let y = b.unwrap_or_default(); + + // unwrap_or_else() lets you specify a closure to run if the Option is None + let z = c.unwrap_or_else(|| if 1 + 1 == 2 { true} else { false }); + + assert_eq!(x, "Hello there".to_owned()); + assert_eq!(y, 0); + assert_eq!(z, true); +} +``` +And then there is this really cool question-mark operator, which comes in very handy once you go multiple functions deep +and keep having to work with more and more `Result`s and `Option`s. The way it works is that, if you have a `None` or an `Error`, +it passes up the handling of this one level higher, by returning out of the function early with a `None` or `Error` value itself. + +Of course, since return types of functions have to be known at compile time, the question-mark operator only works inside +functions that already return `Result` or `Option`. + + +```rust +fn main() { + let x = 5; + let y = 10; + let z = 20; + + match do_something(x, y, z) { + Some(result) => println!("Happy noises, {}", result), + None => println!("Sad noises"), + } +} + +fn do_something(x: i32, y: i32, z: i32) -> Option { + let first_result = do_something_more(x, y)?; + let second_result = do_something_more(first_result, z)?; + + Some(second_result) +} + +fn do_something_more(x: i32, y: i32) -> Option { + Some(x + y) +} +``` + +The advantage of this is that you only have to handle your `None` case exactly once. You don't have to add pattern matching, or +conditionals, or `unwrap()`s all over the place, just a cute little question mark that delegates the handling to some logic +higher up. + +_"But sammy!"_ you say, _"the compiler keeps shouting at me when I use the question mark on Options when my function +returns Result \`<-.\_.->´"_ + +Don't worry my frien! Even this has been considered! + +First of all, why does the compiler get upset? It's because the question-mark operator returns the same type that it's used on, +and `Result` and `Option` are different types. Because of that, I thought I'd have to manually handle `None` cases in all +of my `Result`-returning functions. Until one day, I was reading through some documentation (I know, I know, I'm a nerd who reads +through documentation for fun and not just to find specific things) and discovered [Option::Ok_or()](https://doc.rust-lang.org/std/option/enum.Option.html#method.ok_or). + +> Transforms the `Option` into a `Result`, mapping `Some(v)` to `Ok(v)` and `None` to `Err(err)`. + +This was a life-changer to me, and it was just hiding right there in plain sight. Now I can easily turn a `None` where there shouldn't +be a `None` into an `Error` to pass up with my pawesome question-mark operator! + +```rust +fn main() -> Result<(), String> { + let x = function_that_returns_option().ok_or("error message".to_owned())?; + // Instead of: + // let x = function_that_returns_option().unwrap(); + // or any of the other ways to handle None + + assert_eq!(x, ()); + Ok(x) +} + +fn function_that_returns_option() -> Option<()> { + return Some(()); +} +``` + +The last thing I want to mention is both an example specific to `Option`s, and a more general tip about how I discovered this one. +There is this wonderful friend for all Rust developers, called Clippy. No, not the Paperclip from Microsoft Word, but +[A collection of lints to catch common mistakes and improve your Rust code](https://doc.rust-lang.org/stable/clippy/). +Clippy is automatically installed when you install Rust via `rustup`, and it runs a whole lot of checks against your code +to tell you what you can improve. + +In my case, I had the following piece of code: + +```rust +let insert = ( + tracks::title.eq(match tag.title() { + Some(title) => Some(title.to_string()), + None => None, + }), + tracks::track_number.eq(match tag.track() { + Some(track) => Some(track as i32), + None => None, + }), + tracks::disc_number.eq(match tag.disk() { + Some(track) => Some(track as i32), + None => None, + }), + tracks::path.eq(match path.to_str() { + None => return Err(Error::msg("Could not get path")), + Some(path) => path.to_string(), + }), + tracks::year.eq(match tag.year() { + Some(year) => Some(year as i32), + None => None, + }), + tracks::album_id.eq(match album { + Some(album) => Some(album.id), + None => None, + }), + ); +``` + +This code builds an insert statement for the database holding my music metadata, getting the values from the tags of a file. +The tag fields are all `Option`s, since the tags might be empty. The databse entries are also all `Option`s, (at least on the Rust side, +on the database they are just values marked as possibly being Null). So my intuitive idea to build this was to just go through all the entries, +match the tag, put in `Some(value)` if there is a value, and `None`if there is none. + +It works, it's not wrong, but there is a cleaner and more readable way to do this. And clippy told me right away, I ran it +from my IDE, and it told me: + +> Manual implementation of `Option::map` + +Huh, okay. Let's check the [documentation](https://doc.rust-lang.org/std/option/enum.Option.html#method.map) + +> Maps an `Option` to `Option` by applying a function to a contained value. + +So basically exactly what I did with those `match` statements! +My IDE even had a button to just easily fix this automatically with one click: + +```rust +let insert = ( + tracks::title.eq(tag.title().map(|title| title.to_string())), + tracks::track_number.eq(tag.track().map(|track| track as i32)), + tracks::disc_number.eq(tag.disk().map(|track| track as i32)), + tracks::path.eq(match path.to_str() { + None => return Err(Error::msg("Could not get path")), + Some(path) => path.to_string(), + }), + tracks::year.eq(tag.year().map(|year| year as i32)), + tracks::album_id.eq(album.map(|album| album.id)), + ); +``` + +Great, that looks a lot cleaner immediately! +Note how one of the lines was not changed, that's because that one sets a DB value which is `NOT NULL`, thus if the original `Option` is +a `None` it means something went wrong, and we should abort this insert and return with an Error. + +And with that, we're done with my first blogpost about Rust, with hopefully many more to come! +As I said, I am still learning, and writing this is part of my learning process. That being said, if you find this interesting, +learned something from it, etc., feel free to leave me some feedback! I'd love to hear what you think! +And if I made mistakes, please also tell me. I'm always happy to learn more and to fix those mistakes so others can learn from them too. + +Thank you so much for reading 💜 diff --git a/content/index.md b/content/home.md similarity index 95% rename from content/index.md rename to content/home.md index 1ccd54e..d1a9cc1 100644 --- a/content/index.md +++ b/content/home.md @@ -1,5 +1,6 @@ +++ title = "Hello there!" +template = "page.html" +++ {{ image(src="/profile.jpeg", alt="Profile picture", diff --git a/public/404.html b/public/404.html index 1fd0f26..8b42c84 100644 --- a/public/404.html +++ b/public/404.html @@ -65,7 +65,12 @@ diff --git a/public/about/index.html b/public/about/index.html index 5cc0968..6e871c7 100644 --- a/public/about/index.html +++ b/public/about/index.html @@ -111,7 +111,12 @@ all the relevant projects in my + + @@ -140,7 +156,12 @@ Welcome to the Fediverse 💜.

diff --git a/public/blog/index.html b/public/blog/index.html index f739e30..00edb57 100644 --- a/public/blog/index.html +++ b/public/blog/index.html @@ -57,6 +57,59 @@
+

Learning Rust Part 1: A kitten's guide to Options and Results

+ + + + + + + +
+

To unwrap() or not to unwrap(), that is the question:

+

So I've finally given in and started to learn Rust last month. It's a really cool programming language, +with some interesting differences to what I've used before. (JavaScript and Python, mostly)

+

There are some really pawesome guides out there, "The Rust programming language" is +definitely a must-read in my opinion, and Rustlings is nyamazing for +anyone who likes to learn by actively working through interactive problems.

+

After reading through a lot of those big thorough guides by experienced Rust developers, I've started working on +my first actual Project. I approached the development of this project by just trying to get small parts of it +working in any way I can manage, and then build upon this. In that process, I learned a lot of small subtilties that +guides like the ones named above just can't really cover. This post is for sharing those things, those cool little +tips to make your first Rust project just a little cleaner and more Rust-y. Originally I wanted to make this about a lot +of different topics, but then I've realized that my notes already contain so many things about just one part of Rust: +The Enums Option and Result. So this post will be about those, and hopefully will mark the start of a series on this blog.

+

While reading through this, you might think that the things I'm mentioning are obvious. That's okay, and that's the point. +Nothing is ever completely obvious to everyone, and this is for those like me, who often don't immediately recognize +the "obvious". And, to be honest, I am writing this just as much for myself, writing all of that stuff down to aid me in my +own ongoing learning process.

+

So, let's start!

+ +
+ + + +
+ +
+

The Fediverse is more than just Mastodon