337 lines
31 KiB
HTML
337 lines
31 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
|
||
<head>
|
||
<title>Learning Rust Part 1: A kitten's guide to Options and Results | CherryKitten</title>
|
||
|
||
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1">
|
||
<meta name="robots" content="noodp"/>
|
||
|
||
<link rel="stylesheet" href="https://cherrykitten.dev/style.css">
|
||
<link rel="stylesheet" href="https://cherrykitten.dev/color.css">
|
||
|
||
|
||
<link rel="alternate" type="application/rss+xml" title="RSS" href="https://cherrykitten.dev/ rss.xml
|
||
">
|
||
|
||
</head>
|
||
|
||
<body class="">
|
||
<div class="container">
|
||
|
||
<header class="header">
|
||
<div class="header__inner">
|
||
<div class="header__logo">
|
||
|
||
<a href="https://cherrykitten.dev" style="text-decoration: none;">
|
||
<div class="logo">
|
||
|
||
CherryKitten
|
||
|
||
</div>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<nav class="menu">
|
||
<ul class="menu__inner">
|
||
<li><a href="https://cherrykitten.dev">Home</a></li>
|
||
|
||
<li><a href="https://cherrykitten.dev/about">About me</a></li>
|
||
|
||
<li class="active"><a href="https://cherrykitten.dev/blog">Blog</a></li>
|
||
|
||
<li><a href="https://cherrykitten.dev/contact">Contact</a></li>
|
||
</ul>
|
||
</nav>
|
||
|
||
|
||
|
||
</header>
|
||
|
||
|
||
<div class="content">
|
||
|
||
<div class="post">
|
||
|
||
<h1 class="post-title"><a href="https://cherrykitten.dev/blog/rust-1-options-results/">Learning Rust Part 1: A kitten's guide to Options and Results</a></h1>
|
||
<div class="post-meta-inline">
|
||
|
||
<span class="post-date">
|
||
2023-02-25
|
||
</span>
|
||
|
||
</div>
|
||
|
||
|
||
<span class="post-tags-inline">
|
||
:: tags:
|
||
<a class="post-tag" href="https://cherrykitten.dev/tags/rust/">#Rust</a>
|
||
<a class="post-tag" href="https://cherrykitten.dev/tags/programming/">#programming</a>
|
||
<a class="post-tag" href="https://cherrykitten.dev/tags/code/">#code</a>
|
||
<a class="post-tag" href="https://cherrykitten.dev/tags/learning/">#learning</a></span>
|
||
|
||
|
||
|
||
<div class="post-content">
|
||
<h3 id="to-unwrap-or-not-to-unwrap-that-is-the-question">To unwrap() or not to unwrap(), that is the question:</h3>
|
||
<p>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)</p>
|
||
<p>There are some really pawesome guides out there, <a href="https://doc.rust-lang.org/book/">"The Rust programming language"</a> is
|
||
definitely a <strong>must-read</strong> in my opinion, and <a href="https://github.com/rust-lang/rustlings">Rustlings</a> is nyamazing for
|
||
anyone who likes to learn by actively working through interactive problems.</p>
|
||
<p>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 <code>Option</code> and <code>Result</code>. So this post will be about those, and hopefully will mark the start of a series on this blog.</p>
|
||
<p>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.</p>
|
||
<p>So, let's start!</p>
|
||
<span id="continue-reading"></span>
|
||
<p>Firstly, a very quick introduction. <code>Option</code> and <code>Result</code> are part of the Rust standard library. Quoting the official documentation
|
||
is probably the easiest way to summarize their purpose:</p>
|
||
<blockquote>
|
||
<p>Type <code>Option</code> represents an optional value: every Option is either <code>Some</code> and contains a value, or <code>None</code>, and does not. <code>Option</code> types are very common in Rust code, as they have a number of uses:</p>
|
||
<ul>
|
||
<li>Initial values</li>
|
||
<li>Return values for functions that are not defined over their entire input range (partial functions)</li>
|
||
<li>Return value for otherwise reporting simple errors, where <code>None</code> is returned on error</li>
|
||
<li>Optional struct fields</li>
|
||
<li>Struct fields that can be loaned or “taken”</li>
|
||
<li>Optional function arguments</li>
|
||
<li>Nullable pointers</li>
|
||
<li>Swapping things out of difficult situations</li>
|
||
</ul>
|
||
</blockquote>
|
||
<p>and</p>
|
||
<blockquote>
|
||
<p><code>Result<T, E></code> is the type used for returning and propagating errors. It is an enum with the variants, <code>Ok(T)</code>, representing success and containing a value, and <code>Err(E)</code>, representing error and containing an error value.</p>
|
||
</blockquote>
|
||
<p>At first, it seems so easy to just add a quick <code>.unwrap()</code> after every <code>Option</code> or <code>Result</code>, but this comes with
|
||
the disadvantage of your code <a href="https://doc.rust-lang.org/std/macro.panic.html">panicking</a> 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.</p>
|
||
<p>So, what can you do instead?</p>
|
||
<p>First of all, don't use <code>unwrap()</code> 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 <code>Ok</code> or <code>Some</code>.</p>
|
||
<p>In some cases, you actually want the program to panic. But even then, there is a slightly better way. You can use <code>expect("foo")</code>
|
||
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 <em>expected</em> to happen.</p>
|
||
<pre data-lang="rust" style="background-color:#1f1d29;color:#ffffff;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#9bced7;">fn </span><span style="color:#34738e;">main</span><span>() {
|
||
</span><span> </span><span style="font-style:italic;color:#9bced7;">let</span><span> x </span><span style="color:#ea6f91;">= </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(</span><span style="color:#f1ca93;">"Hello, World"</span><span>);
|
||
</span><span> </span><span style="color:#403c58;">// There is no actual "None" type/value in Rust,
|
||
</span><span> </span><span style="color:#403c58;">// this "None" is more specifically Option::None
|
||
</span><span> </span><span style="font-style:italic;color:#9bced7;">let</span><span> y: </span><span style="font-style:italic;color:#66d9ef;">Option</span><span><</span><span style="font-style:italic;color:#66d9ef;">String</span><span>> </span><span style="color:#ea6f91;">= </span><span style="font-style:italic;color:#66d9ef;">None</span><span>;
|
||
</span><span>
|
||
</span><span> </span><span style="font-style:italic;color:#9bced7;">let</span><span> z </span><span style="color:#ea6f91;">=</span><span> x.</span><span style="color:#66d9ef;">unwrap</span><span>(); </span><span style="color:#403c58;">// We explicitly set this to Some, so we can safely unwrap it
|
||
</span><span> </span><span style="font-style:italic;color:#9bced7;">let</span><span> rip </span><span style="color:#ea6f91;">=</span><span> y.</span><span style="color:#66d9ef;">expect</span><span>(</span><span style="color:#f1ca93;">"expected y to not be None"</span><span>);
|
||
</span><span>
|
||
</span><span> println!(</span><span style="color:#f1ca93;">"</span><span style="color:#c3a5e6;">{}</span><span style="color:#f1ca93;">, </span><span style="color:#c3a5e6;">{}</span><span style="color:#f1ca93;"> will never print because it panics above"</span><span>, z, rip);
|
||
</span><span>}
|
||
</span></code></pre>
|
||
<p>There are also the non-panicking siblings of <code>unwrap()</code>, like <code>unwrap_or()</code>, <code>unwrap_or_else()</code> and <code>unwrap_or_default()</code>.</p>
|
||
<pre data-lang="rust" style="background-color:#1f1d29;color:#ffffff;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#9bced7;">fn </span><span style="color:#34738e;">main</span><span>() {
|
||
</span><span> </span><span style="font-style:italic;color:#9bced7;">let</span><span> a: </span><span style="font-style:italic;color:#66d9ef;">Option</span><span><</span><span style="font-style:italic;color:#66d9ef;">String</span><span>> </span><span style="color:#ea6f91;">= </span><span style="font-style:italic;color:#66d9ef;">None</span><span>;
|
||
</span><span> </span><span style="font-style:italic;color:#9bced7;">let</span><span> b: </span><span style="font-style:italic;color:#66d9ef;">Option</span><span><</span><span style="font-style:italic;color:#9bced7;">i32</span><span>> </span><span style="color:#ea6f91;">= </span><span style="font-style:italic;color:#66d9ef;">None</span><span>;
|
||
</span><span> </span><span style="font-style:italic;color:#9bced7;">let</span><span> c: </span><span style="font-style:italic;color:#66d9ef;">Option</span><span><</span><span style="font-style:italic;color:#9bced7;">bool</span><span>> </span><span style="color:#ea6f91;">= </span><span style="font-style:italic;color:#66d9ef;">None</span><span>;
|
||
</span><span>
|
||
</span><span> </span><span style="color:#403c58;">// unwrap_or() lets you supply a specific value to use if the Option is None
|
||
</span><span> </span><span style="font-style:italic;color:#9bced7;">let</span><span> x </span><span style="color:#ea6f91;">=</span><span> a.</span><span style="color:#66d9ef;">unwrap_or</span><span>(</span><span style="color:#f1ca93;">"Hello there"</span><span>.</span><span style="color:#66d9ef;">to_owned</span><span>());
|
||
</span><span>
|
||
</span><span> </span><span style="color:#403c58;">// unwrap_or_default() uses the types default value if the Option is None
|
||
</span><span> </span><span style="font-style:italic;color:#9bced7;">let</span><span> y </span><span style="color:#ea6f91;">=</span><span> b.</span><span style="color:#66d9ef;">unwrap_or_default</span><span>();
|
||
</span><span>
|
||
</span><span> </span><span style="color:#403c58;">// unwrap_or_else() lets you specify a closure to run if the Option is None
|
||
</span><span> </span><span style="font-style:italic;color:#9bced7;">let</span><span> z </span><span style="color:#ea6f91;">=</span><span> c.</span><span style="color:#66d9ef;">unwrap_or_else</span><span>(|| </span><span style="color:#ea6f91;">if </span><span style="color:#c3a5e6;">1 </span><span style="color:#ea6f91;">+ </span><span style="color:#c3a5e6;">1 </span><span style="color:#ea6f91;">== </span><span style="color:#c3a5e6;">2 </span><span>{ </span><span style="color:#c3a5e6;">true</span><span>} </span><span style="color:#ea6f91;">else </span><span>{ </span><span style="color:#c3a5e6;">false </span><span>});
|
||
</span><span>
|
||
</span><span> assert_eq!(x, </span><span style="color:#f1ca93;">"Hello there"</span><span>.</span><span style="color:#66d9ef;">to_owned</span><span>());
|
||
</span><span> assert_eq!(y, </span><span style="color:#c3a5e6;">0</span><span>);
|
||
</span><span> assert_eq!(z, </span><span style="color:#c3a5e6;">true</span><span>);
|
||
</span><span>}
|
||
</span></code></pre>
|
||
<p>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 <code>Result</code>s and <code>Option</code>s. The way it works is that, if you have a <code>None</code> or an <code>Error</code>,
|
||
it passes up the handling of this one level higher, by returning out of the function early with a <code>None</code> or <code>Error</code> value itself.</p>
|
||
<p>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 <code>Result</code> or <code>Option</code>.</p>
|
||
<pre data-lang="rust" style="background-color:#1f1d29;color:#ffffff;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#9bced7;">fn </span><span style="color:#34738e;">main</span><span>() {
|
||
</span><span> </span><span style="font-style:italic;color:#9bced7;">let</span><span> x </span><span style="color:#ea6f91;">= </span><span style="color:#c3a5e6;">5</span><span>;
|
||
</span><span> </span><span style="font-style:italic;color:#9bced7;">let</span><span> y </span><span style="color:#ea6f91;">= </span><span style="color:#c3a5e6;">10</span><span>;
|
||
</span><span> </span><span style="font-style:italic;color:#9bced7;">let</span><span> z </span><span style="color:#ea6f91;">= </span><span style="color:#c3a5e6;">20</span><span>;
|
||
</span><span>
|
||
</span><span> </span><span style="color:#ea6f91;">match </span><span style="color:#66d9ef;">do_something</span><span>(x, y, z) {
|
||
</span><span> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(result) </span><span style="color:#ea6f91;">=> </span><span>println!(</span><span style="color:#f1ca93;">"Happy noises, </span><span style="color:#c3a5e6;">{}</span><span style="color:#f1ca93;">"</span><span>, result),
|
||
</span><span> </span><span style="font-style:italic;color:#66d9ef;">None </span><span style="color:#ea6f91;">=> </span><span>println!(</span><span style="color:#f1ca93;">"Sad noises"</span><span>),
|
||
</span><span> }
|
||
</span><span>}
|
||
</span><span>
|
||
</span><span style="font-style:italic;color:#9bced7;">fn </span><span style="color:#34738e;">do_something</span><span>(</span><span style="font-style:italic;color:#f1ca93;">x</span><span>: </span><span style="font-style:italic;color:#9bced7;">i32</span><span>, </span><span style="font-style:italic;color:#f1ca93;">y</span><span>: </span><span style="font-style:italic;color:#9bced7;">i32</span><span>, </span><span style="font-style:italic;color:#f1ca93;">z</span><span>: </span><span style="font-style:italic;color:#9bced7;">i32</span><span>) -> </span><span style="font-style:italic;color:#66d9ef;">Option</span><span><</span><span style="font-style:italic;color:#9bced7;">i32</span><span>> {
|
||
</span><span> </span><span style="font-style:italic;color:#9bced7;">let</span><span> first_result </span><span style="color:#ea6f91;">= </span><span style="color:#66d9ef;">do_something_more</span><span>(x, y)</span><span style="color:#ea6f91;">?</span><span>;
|
||
</span><span> </span><span style="font-style:italic;color:#9bced7;">let</span><span> second_result </span><span style="color:#ea6f91;">= </span><span style="color:#66d9ef;">do_something_more</span><span>(first_result, z)</span><span style="color:#ea6f91;">?</span><span>;
|
||
</span><span>
|
||
</span><span> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(second_result)
|
||
</span><span>}
|
||
</span><span>
|
||
</span><span style="font-style:italic;color:#9bced7;">fn </span><span style="color:#34738e;">do_something_more</span><span>(</span><span style="font-style:italic;color:#f1ca93;">x</span><span>: </span><span style="font-style:italic;color:#9bced7;">i32</span><span>, </span><span style="font-style:italic;color:#f1ca93;">y</span><span>: </span><span style="font-style:italic;color:#9bced7;">i32</span><span>) -> </span><span style="font-style:italic;color:#66d9ef;">Option</span><span><</span><span style="font-style:italic;color:#9bced7;">i32</span><span>> {
|
||
</span><span> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(x </span><span style="color:#ea6f91;">+</span><span> y)
|
||
</span><span>}
|
||
</span></code></pre>
|
||
<p>The advantage of this is that you only have to handle your <code>None</code> case exactly once. You don't have to add pattern matching, or
|
||
conditionals, or <code>unwrap()</code>s all over the place, just a cute little question mark that delegates the handling to some logic
|
||
higher up.</p>
|
||
<p><em>"But sammy!"</em> you say, <em>"the compiler keeps shouting at me when I use the question mark on Options when my function
|
||
returns Result `<-._.->´"</em></p>
|
||
<p>Don't worry my frien! Even this has been considered!</p>
|
||
<p>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 <code>Result</code> and <code>Option</code> are different types. Because of that, I thought I'd have to manually handle <code>None</code> cases in all
|
||
of my <code>Result</code>-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 <a href="https://doc.rust-lang.org/std/option/enum.Option.html#method.ok_or">Option::Ok_or()</a>.</p>
|
||
<blockquote>
|
||
<p>Transforms the <code>Option<T></code> into a <code>Result<T, E></code>, mapping <code>Some(v)</code> to <code>Ok(v)</code> and <code>None</code> to <code>Err(err)</code>.</p>
|
||
</blockquote>
|
||
<p>This was a life-changer to me, and it was just hiding right there in plain sight. Now I can easily turn a <code>None</code> where there shouldn't
|
||
be a <code>None</code> into an <code>Error</code> to pass up with my pawesome question-mark operator!</p>
|
||
<pre data-lang="rust" style="background-color:#1f1d29;color:#ffffff;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#9bced7;">fn </span><span style="color:#34738e;">main</span><span>() -> </span><span style="font-style:italic;color:#66d9ef;">Result</span><span><(), </span><span style="font-style:italic;color:#66d9ef;">String</span><span>> {
|
||
</span><span> </span><span style="font-style:italic;color:#9bced7;">let</span><span> x </span><span style="color:#ea6f91;">= </span><span style="color:#66d9ef;">function_that_returns_option</span><span>().</span><span style="color:#66d9ef;">ok_or</span><span>(</span><span style="color:#f1ca93;">"error message"</span><span>.</span><span style="color:#66d9ef;">to_owned</span><span>())</span><span style="color:#ea6f91;">?</span><span>;
|
||
</span><span> </span><span style="color:#403c58;">// Instead of:
|
||
</span><span> </span><span style="color:#403c58;">// let x = function_that_returns_option().unwrap();
|
||
</span><span> </span><span style="color:#403c58;">// or any of the other ways to handle None
|
||
</span><span>
|
||
</span><span> assert_eq!(x, ());
|
||
</span><span> </span><span style="font-style:italic;color:#66d9ef;">Ok</span><span>(x)
|
||
</span><span>}
|
||
</span><span>
|
||
</span><span style="font-style:italic;color:#9bced7;">fn </span><span style="color:#34738e;">function_that_returns_option</span><span>() -> </span><span style="font-style:italic;color:#66d9ef;">Option</span><span><()> {
|
||
</span><span> </span><span style="color:#ea6f91;">return </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(());
|
||
</span><span>}
|
||
</span></code></pre>
|
||
<p>The last thing I want to mention is both an example specific to <code>Option</code>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 href="https://doc.rust-lang.org/stable/clippy/">A collection of lints to catch common mistakes and improve your Rust code</a>.
|
||
Clippy is automatically installed when you install Rust via <code>rustup</code>, and it runs a whole lot of checks against your code
|
||
to tell you what you can improve.</p>
|
||
<p>In my case, I had the following piece of code:</p>
|
||
<pre data-lang="rust" style="background-color:#1f1d29;color:#ffffff;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#9bced7;">let</span><span> insert </span><span style="color:#ea6f91;">= </span><span>(
|
||
</span><span> tracks::title.</span><span style="color:#66d9ef;">eq</span><span>(</span><span style="color:#ea6f91;">match</span><span> tag.</span><span style="color:#66d9ef;">title</span><span>() {
|
||
</span><span> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(title) </span><span style="color:#ea6f91;">=> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(title.</span><span style="color:#66d9ef;">to_string</span><span>()),
|
||
</span><span> </span><span style="font-style:italic;color:#66d9ef;">None </span><span style="color:#ea6f91;">=> </span><span style="font-style:italic;color:#66d9ef;">None</span><span>,
|
||
</span><span> }),
|
||
</span><span> tracks::track_number.</span><span style="color:#66d9ef;">eq</span><span>(</span><span style="color:#ea6f91;">match</span><span> tag.</span><span style="color:#66d9ef;">track</span><span>() {
|
||
</span><span> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(track) </span><span style="color:#ea6f91;">=> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(track </span><span style="color:#ea6f91;">as </span><span style="font-style:italic;color:#9bced7;">i32</span><span>),
|
||
</span><span> </span><span style="font-style:italic;color:#66d9ef;">None </span><span style="color:#ea6f91;">=> </span><span style="font-style:italic;color:#66d9ef;">None</span><span>,
|
||
</span><span> }),
|
||
</span><span> tracks::disc_number.</span><span style="color:#66d9ef;">eq</span><span>(</span><span style="color:#ea6f91;">match</span><span> tag.</span><span style="color:#66d9ef;">disk</span><span>() {
|
||
</span><span> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(track) </span><span style="color:#ea6f91;">=> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(track </span><span style="color:#ea6f91;">as </span><span style="font-style:italic;color:#9bced7;">i32</span><span>),
|
||
</span><span> </span><span style="font-style:italic;color:#66d9ef;">None </span><span style="color:#ea6f91;">=> </span><span style="font-style:italic;color:#66d9ef;">None</span><span>,
|
||
</span><span> }),
|
||
</span><span> tracks::path.</span><span style="color:#66d9ef;">eq</span><span>(</span><span style="color:#ea6f91;">match</span><span> path.</span><span style="color:#66d9ef;">to_str</span><span>() {
|
||
</span><span> </span><span style="font-style:italic;color:#66d9ef;">None </span><span style="color:#ea6f91;">=> return </span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(Error::msg(</span><span style="color:#f1ca93;">"Could not get path"</span><span>)),
|
||
</span><span> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(path) </span><span style="color:#ea6f91;">=></span><span> path.</span><span style="color:#66d9ef;">to_string</span><span>(),
|
||
</span><span> }),
|
||
</span><span> tracks::year.</span><span style="color:#66d9ef;">eq</span><span>(</span><span style="color:#ea6f91;">match</span><span> tag.</span><span style="color:#66d9ef;">year</span><span>() {
|
||
</span><span> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(year) </span><span style="color:#ea6f91;">=> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(year </span><span style="color:#ea6f91;">as </span><span style="font-style:italic;color:#9bced7;">i32</span><span>),
|
||
</span><span> </span><span style="font-style:italic;color:#66d9ef;">None </span><span style="color:#ea6f91;">=> </span><span style="font-style:italic;color:#66d9ef;">None</span><span>,
|
||
</span><span> }),
|
||
</span><span> tracks::album_id.</span><span style="color:#66d9ef;">eq</span><span>(</span><span style="color:#ea6f91;">match</span><span> album {
|
||
</span><span> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(album) </span><span style="color:#ea6f91;">=> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(album.id),
|
||
</span><span> </span><span style="font-style:italic;color:#66d9ef;">None </span><span style="color:#ea6f91;">=> </span><span style="font-style:italic;color:#66d9ef;">None</span><span>,
|
||
</span><span> }),
|
||
</span><span> );
|
||
</span></code></pre>
|
||
<p>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 <code>Option</code>s, since the tags might be empty. The databse entries are also all <code>Option</code>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 <code>Some(value)</code> if there is a value, and <code>None</code>if there is none.</p>
|
||
<p>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:</p>
|
||
<blockquote>
|
||
<p>Manual implementation of <code>Option::map</code></p>
|
||
</blockquote>
|
||
<p>Huh, okay. Let's check the <a href="https://doc.rust-lang.org/std/option/enum.Option.html#method.map">documentation</a> </p>
|
||
<blockquote>
|
||
<p>Maps an <code>Option<T></code> to <code>Option<U></code> by applying a function to a contained value.</p>
|
||
</blockquote>
|
||
<p>So basically exactly what I did with those <code>match</code> statements!
|
||
My IDE even had a button to just easily fix this automatically with one click:</p>
|
||
<pre data-lang="rust" style="background-color:#1f1d29;color:#ffffff;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#9bced7;">let</span><span> insert </span><span style="color:#ea6f91;">= </span><span>(
|
||
</span><span> tracks::title.</span><span style="color:#66d9ef;">eq</span><span>(tag.</span><span style="color:#66d9ef;">title</span><span>().</span><span style="color:#66d9ef;">map</span><span>(|</span><span style="font-style:italic;color:#f1ca93;">title</span><span>| title.</span><span style="color:#66d9ef;">to_string</span><span>())),
|
||
</span><span> tracks::track_number.</span><span style="color:#66d9ef;">eq</span><span>(tag.</span><span style="color:#66d9ef;">track</span><span>().</span><span style="color:#66d9ef;">map</span><span>(|</span><span style="font-style:italic;color:#f1ca93;">track</span><span>| track </span><span style="color:#ea6f91;">as </span><span style="font-style:italic;color:#9bced7;">i32</span><span>)),
|
||
</span><span> tracks::disc_number.</span><span style="color:#66d9ef;">eq</span><span>(tag.</span><span style="color:#66d9ef;">disk</span><span>().</span><span style="color:#66d9ef;">map</span><span>(|</span><span style="font-style:italic;color:#f1ca93;">track</span><span>| track </span><span style="color:#ea6f91;">as </span><span style="font-style:italic;color:#9bced7;">i32</span><span>)),
|
||
</span><span> tracks::path.</span><span style="color:#66d9ef;">eq</span><span>(</span><span style="color:#ea6f91;">match</span><span> path.</span><span style="color:#66d9ef;">to_str</span><span>() {
|
||
</span><span> </span><span style="font-style:italic;color:#66d9ef;">None </span><span style="color:#ea6f91;">=> return </span><span style="font-style:italic;color:#66d9ef;">Err</span><span>(Error::msg(</span><span style="color:#f1ca93;">"Could not get path"</span><span>)),
|
||
</span><span> </span><span style="font-style:italic;color:#66d9ef;">Some</span><span>(path) </span><span style="color:#ea6f91;">=></span><span> path.</span><span style="color:#66d9ef;">to_string</span><span>(),
|
||
</span><span> }),
|
||
</span><span> tracks::year.</span><span style="color:#66d9ef;">eq</span><span>(tag.</span><span style="color:#66d9ef;">year</span><span>().</span><span style="color:#66d9ef;">map</span><span>(|</span><span style="font-style:italic;color:#f1ca93;">year</span><span>| year </span><span style="color:#ea6f91;">as </span><span style="font-style:italic;color:#9bced7;">i32</span><span>)),
|
||
</span><span> tracks::album_id.</span><span style="color:#66d9ef;">eq</span><span>(album.</span><span style="color:#66d9ef;">map</span><span>(|</span><span style="font-style:italic;color:#f1ca93;">album</span><span>| album.id)),
|
||
</span><span> );
|
||
</span></code></pre>
|
||
<p>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 <code>NOT NULL</code>, thus if the original <code>Option</code> is
|
||
a <code>None</code> it means something went wrong, and we should abort this insert and return with an Error.</p>
|
||
<p>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.</p>
|
||
<p>Thank you so much for reading 💜</p>
|
||
|
||
</div>
|
||
|
||
|
||
<div class="pagination">
|
||
<div class="pagination__title">
|
||
<span class="pagination__title-h">More posts!</span>
|
||
<hr />
|
||
</div>
|
||
<div class="pagination__buttons">
|
||
<span class="button previous">
|
||
<a href="https://cherrykitten.dev/blog/fediverse-isnt-just-mastodon/">
|
||
<span class="button__icon">←</span>
|
||
<span class="button__text">The Fediverse is more than just Mastodon</span>
|
||
</a>
|
||
</span>
|
||
|
||
|
||
<span class="button next">
|
||
<a href="https://cherrykitten.dev/blog/being-queer-and-otherkin/">
|
||
<span class="button__text">Being Queer and Otherkin</span>
|
||
<span class="button__icon">→</span>
|
||
</a>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
<footer class="footer">
|
||
<div class="footer__inner">
|
||
<div class="copyright copyright--user">
|
||
<a href="https://liberapay.com/CherryKitten/donate"><img
|
||
alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a>
|
||
|
||
<span>©
|
||
2023
|
||
- CherryKitten</span><br>
|
||
|
||
<span><a href="/impressum">Impressum</a></span>
|
||
|
||
<span><a href="/rss.xml">RSS</a></span>
|
||
|
||
<span onclick="alert('Nya!')">🐱</span></div>
|
||
</div>
|
||
</footer>
|
||
|
||
|
||
</div>
|
||
</body>
|
||
|
||
</html>
|