{"id":72,"date":"2022-07-14T04:09:01","date_gmt":"2022-07-14T04:09:01","guid":{"rendered":"https:\/\/blog.jdkendall.com\/?p=72"},"modified":"2022-07-23T15:11:57","modified_gmt":"2022-07-23T15:11:57","slug":"i-am-error","status":"publish","type":"post","link":"https:\/\/blog.jdkendall.com\/?p=72","title":{"rendered":"I AM ERROR"},"content":{"rendered":"\n<p>With the renewed interest between my friends and I in development of Medusa, our 8-bit love letter to the NES era of puzzle games, I&#8217;ve engaged with the Rust programming language for the construction of the engine. The language makes a certain design pattern first class, which I want to discuss here.<\/p>\n\n\n\n<p><em>(Disclaimer: I am yet new to Rust, so my terminology or understanding may have holes. Friendly corrections are always welcome!)<\/em><\/p>\n\n\n\n<p>Null values, missing data, errors, crashes &#8211; we see these all-too-frequently in the world of software. Games are no exception, as the post&#8217;s title attests. We can trace many of these problems back to poor handling of data, wherein validation was considered an afterthought or omitted altogether due to constraints of language or hardware.<\/p>\n\n\n\n<p>With modern hardware and languages, we no longer have to make a tough decision about handling data outside of the hottest loops on the bleeding edge, and in some cases, we can even keep some of the benefits of data validation assurances with zero cost abstractions. With Rust, that&#8217;s the intentional expression of success\/failure states as well as data presence through data structures with limited (sometimes no) overhead.<\/p>\n\n\n\n<p>In Rust, these issues are addressed primarily by two types: <code>Option&lt;T&gt;<\/code> for possibly-empty values, and <code>Result&lt;T,U&gt;<\/code> for possibly-failing logic. Rust goes a step further than some other languages that implement these constructs, by forbidding the use of <code>null<\/code> values to circumvent the use of <code>Option<\/code> and by intentionally leaving exceptions off of the table for dealing with failing logic.<\/p>\n\n\n\n<p>Through these two language design decisions, Rust manages to coerce the programmer into handling these edge cases that would otherwise be hand-waved away. On the architectural level for the software, this changes the landscape: in safe\/sound Rust, you can trust that a function which is not labeled with <code>Result<\/code> will not result in errors, and a function which is not labeled with <code>Option<\/code> will never be missing a value<sup>\u2020<\/sup>.<\/p>\n\n\n\n<p>I&#8217;ve used similar constructs in the world of Scala to great effect; there was a solace in knowing that if the compiler type-checked the code with no issue, the likelihood of a logical error of this type was very low &#8211; a much better confidence level than if I were to check it myself in, say, C or Java. Scala has many more constructs that continue to improve architecture (<code>NonEmptyList<\/code> as an example) which Rust does not include in its core language library; options for these can be found via Cargo crates (third-party code libraries) but as of yet I have not found myself needing more than the core pieces.<\/p>\n\n\n\n<p>So what makes this so effective? Why do I want others to also leave the well-traveled path of exceptions and null checks to use these new types? Doesn&#8217;t this introduce more overhead for the programmer?<\/p>\n\n\n\n<p>My answer to all of this is simply: these constructs enforce invariants in the code. Things that cannot, or should not, change about the logic of the application, and which are often taken for granted by the programmer even when they are not strictly true. In a sense, they force the programmer to &#8220;prove&#8221; that they are handling the logic correctly by restricting what types can be used to represent the state of the program.<\/p>\n\n\n\n<p><em>(State restriction through data types is a separate topic but closely related. I may do another post on that in the near future, as I continue to explore the world of Bevy&#8217;s ECS for Medusa.)<\/em><\/p>\n\n\n\n<p>Consider the following code, written in a hopefully familiar pseudo-language:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>let players: List&lt;Player> = List(\n    Player { name: \"Miller\", maxHP: 5, scores: List(10, 30, 15, 8, 4) },\n    Player { name: \"Howard\", maxHP: 0, scores: List(20, 17, 8, 41) },\n    Player { name: \"Jan\", maxHP: 12, scores: List(15, 9, 4, 12, 6) },\n)\n\nfunction calculateMatchScore(players: List&lt;Player>, matchIndex: int): int {\n    let matchScoreTotal = 0\n\n    for (player in players) {\n        matchScoreTotal += player.scores&#91;matchIndex - 1]\n    }\n\n    return matchScoreTotal\n}\n\nlet score = calculateMatchScore(players, 5) \/\/ Round 5!\nprint(score)<\/code><\/pre>\n\n\n\n<p>While contrived, it seems like a reasonable facsimile of a real world scenario: given some list of players we have, we want to get the total score from the match between all the players. Just loop through the players&#8217; score totals and&#8230;!<\/p>\n\n\n\n<p>&#8230;Crash at runtime. All due to Howard&#8217;s player having a missing score in his list &#8211; somehow, maybe he didn&#8217;t participate in the game, maybe the data was just never written to the list at all. Either way, now we have a problem. The programmer assumed the invariant that the players&#8217; scores would always have the entries for their match index, and this turned out to be a false assumption with the above code. Worse, the compiler was happy to let this happen.<\/p>\n\n\n\n<p>So what&#8217;s the difference with Rust, Scala, and other languages that heavily encourage these types as part of daily programming? Let&#8217;s see&#8230;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>let players: List&lt;Player> = List(\n    Player { name: \"Miller\", maxHP: 5, scores: List(10, 30, 15, 8, 4) },\n    Player { name: \"Howard\", maxHP: 0, scores: List(20, 17, 8, 41) },\n    Player { name: \"Jan\", maxHP: 12, scores: List(15, 9, 4, 12, 6) },\n)\n\nfunction calculateMatchScore(players: List&lt;Player>, matchIndex: int): Result&lt;int, string> {\n    let matchScoreTotal = 0\n\n    for (player in players) {\n        let maybeScore: Option&lt;int> = player.scores.get(matchIndex - 1)\n    \n        if(maybeScore.isNone) {\n            return Err(f\"Player ${player.name} is missing a score for match ${matchIndex}!\")\n        }\n\n        \/\/ Destructure as Some\n        let Some(score) = maybeScore\n        matchScoreTotal += score\n    }\n\n    return Ok(matchScoreTotal)\n}\n\nlet maybeScore: Result&lt;int, string> = calculateMatchScore(players, 5) \/\/ Round 5!\nif(maybeScore.isOk) {\n    \/\/ Destructure as Ok\n    let Ok(score) = maybeScore\n    print(\"The game's score was: ${score}\")\n} else {\n    \/\/ Destructure as Err\n    let Err(warning) = maybeScore\n    print(\"A data error has occurred! ${warning}\")\n}<\/code><\/pre>\n\n\n\n<p>So what&#8217;s different? It&#8217;s a lot more code, though in languages like Rust and Scala there are ways to avoid the boilerplate using special syntax or functions. Here, I&#8217;ve left it relatively plain for the sake of example.<\/p>\n\n\n\n<p>What else? The function now correctly states that it won&#8217;t always find the score value as part of the signature, eg it returns a <code>Result<\/code>. If it doesn&#8217;t, it returns an <code>Err<\/code> type of <code>Result<\/code> which contains the warning message. If all goes well though, it will return an <code>Ok<\/code> type of <code>Result<\/code> which contains the score.<\/p>\n\n\n\n<p>The access to the value inside of the <code>List<\/code> has changed too. It&#8217;s now done via a <code>get()<\/code> call which returns an <code>Option<\/code> . If the data is not present, the <code>Option<\/code> returned will be <code>None<\/code> type, meaning that index doesn&#8217;t exist. If the data is present, a <code>Some<\/code> type will be returned instead and this can have the score value taken out of it.<\/p>\n\n\n\n<p>Finally, before we can use the results of either of our fallible function calls (<code>_.get()<\/code> and <code>calculateMatchScore()<\/code> respectively)  we must deal with these codified invariants. The programmer can no longer hand-wave the inevitable failures, and instead must have a logical response &#8211; even if it&#8217;s just changing the shape to share with the caller (<code>calculateMatchScore<\/code> converts missing data into an error). In doing so, someone in that chain must deal with the problem before any processing can continue.<\/p>\n\n\n\n<p>These can&#8217;t prevent all errors, of course. There will always be some way for a programmer to cheat the system if they truly want to do so; squelching exceptions just becomes returning a fake <code>Ok<\/code> or replacing a failure with a dummy value &#8220;because it will never happen&#8221;. That said, unlike the other way of doing things, this way forces the programmer to explicitly make that bad decision &#8212; no more accidents, and it&#8217;s much easier to learn from an obvious mistake than a hidden one.<\/p>\n\n\n\n<p>This design philosophy can be applied at large, too: if you must think about designing for both success and failure, you in turn will be forced to think through the broad strokes of what your system will encounter. This bubbles upward, revealing flaws in designs that might otherwise not appear until much later. Nulls in databases, queues that reach saturation, not having permission to read or write files all happen sooner or later, but when thinking through &#8220;success and failure&#8221;, the likelihood any of these go unaddressed in the primary case lessens dramatically.<\/p>\n\n\n\n<p>I wonder if there&#8217;s a high level systems specification language out there that does this kind of constraints and invariant checking&#8230; A question for another time.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"has-small-font-size\">\u2020 This is intentionally ignoring some of the finer details of Rust for simplicity of discussion. There exist a handful of cases where the broad statement does not apply, but these cases are also compiler-checked in their own fashion. Propagating invalid state still requires intentional action in most of these cases, or simply does not matter in others (eg, panic!() unwinding the program.)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>With the renewed interest between my friends and I in development of Medusa, our 8-bit love letter to the NES era of puzzle games, I&#8217;ve engaged with the Rust programming language for the construction of the engine. The language makes a certain design pattern first class, which I want to discuss here. (Disclaimer: I am&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-72","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/blog.jdkendall.com\/index.php?rest_route=\/wp\/v2\/posts\/72","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.jdkendall.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.jdkendall.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jdkendall.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jdkendall.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=72"}],"version-history":[{"count":6,"href":"https:\/\/blog.jdkendall.com\/index.php?rest_route=\/wp\/v2\/posts\/72\/revisions"}],"predecessor-version":[{"id":92,"href":"https:\/\/blog.jdkendall.com\/index.php?rest_route=\/wp\/v2\/posts\/72\/revisions\/92"}],"wp:attachment":[{"href":"https:\/\/blog.jdkendall.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=72"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jdkendall.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=72"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jdkendall.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=72"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}