{"id":4901,"date":"2022-06-22T18:07:06","date_gmt":"2022-06-22T23:07:06","guid":{"rendered":"https:\/\/neosmart.net\/blog\/?p=4901"},"modified":"2022-06-23T15:51:52","modified_gmt":"2022-06-23T20:51:52","slug":"prettysize-0-3-release-and-a-weakness-in-rusts-type-system","status":"publish","type":"post","link":"https:\/\/neosmart.net\/blog\/prettysize-0-3-release-and-a-weakness-in-rusts-type-system\/","title":{"rendered":"PrettySize 0.3 release and a weakness in rust&#8217;s type system"},"content":{"rendered":"<p><a href=\"https:\/\/neosmart.net\/blog\/wp-content\/uploads\/mannequin-size.png\" rel=\"follow\"><img loading=\"lazy\" decoding=\"async\" class=\"alignright size-thumbnail wp-image-4074 colorbox-4901\" src=\"https:\/\/neosmart.net\/blog\/wp-content\/uploads\/mannequin-size-150x150.png\" alt=\"PrettySize\" width=\"150\" height=\"150\" srcset=\"https:\/\/neosmart.net\/blog\/wp-content\/uploads\/mannequin-size-150x150.png 150w, https:\/\/neosmart.net\/blog\/wp-content\/uploads\/mannequin-size-600x600.png 600w, https:\/\/neosmart.net\/blog\/wp-content\/uploads\/mannequin-size-1024x1024.png 1024w, https:\/\/neosmart.net\/blog\/wp-content\/uploads\/mannequin-size-300x300.png 300w, https:\/\/neosmart.net\/blog\/wp-content\/uploads\/mannequin-size.png 1200w\" sizes=\"auto, (max-width: 150px) 100vw, 150px\" \/><\/a>I&#8217;m happy to announce that a new version of <code>size<\/code>, the <a href=\"https:\/\/github.com\/neosmart\/PrettySize.net\" rel=\"nofollow\">PrettySize.NET<\/a> port for rust, <a href=\"https:\/\/crates.io\/crates\/size\" rel=\"nofollow\">has been released<\/a> and includes a number of highly requested features and improvements.<\/p>\n<p>The last major release of the <code>size<\/code> crate was 0.1.2, released in December of 2018. It was feature complete with regards to its original purpose: the (automatic) textual formatting of file sizes for human-readable printing\/display purposes. It would automatically take a file size, pick the appropriate unit (KB, MB, GB, etc) to display the final result in, and choose a suitable precision for the floating numeric component. It had support for both base-10 (KB, MB, GB, etc) and base-2 (KiB, MiB, GiB, etc) types, and the user could choose between them as well as override how the unit was formatted. In short, it did one thing and did it right.<\/p>\n<p><!--more--><\/p>\n<h4>A brief recap of the <code>size<\/code> crate to date<\/h4>\n<p>Some time after its release, <a href=\"https:\/\/github.com\/neosmart\/prettysize-rs\/issues\/1\" rel=\"nofollow\">there was a request made<\/a> to add support for mathematical operations on strongly-typed <code>Size<\/code> types (without having to go to an intermediate &#8220;raw&#8221; bytes value and back) that I originally approached with some gusto, but ended up dismayed by some restrictions of the rust type system that made it difficult to write generic code that could support the full gamut of what a user could reasonably expect to be able to do (more on this later).<\/p>\n<p>As the <code>size<\/code> crate covered the functionality we needed out of it here at NeoSmart Technologies and as there were valid workarounds for composing\/calculating sizes (<a href=\"https:\/\/docs.rs\/size\/0.3.0\/size\/struct.Size.html#method.bytes\" rel=\"nofollow\">via the <code>.bytes()<\/code> escape hatch<\/a>), there wasn&#8217;t a pressing need to tackle those limitations and other projects took priority meaning <code>size<\/code> didn&#8217;t see any updates since then. But it never sat well with me that I left my git working tree in an unclean state and had open issues languishing unresolved, and from time to time I would always think of going back and issuing an update&#8230; but you know how that goes.<\/p>\n<p>However, in a recent discussion apropos &#8220;genuine limitations of the rust type system that frustrate people that otherwise love rust&#8221; I raised the issue that I ran into and that finally got me hot and bothered enough to finally tackle a new <code>size<\/code> release with the requested support for mathematical operators and more.<\/p>\n<h4>Rust&#8217;s problem with commutative mathematical operations<\/h4>\n<p>So what&#8217;s this about a problem in rust&#8217;s type system? Well, with the caveat that it&#8217;s importance is almost certainly overinflated in my eyes, the issue lies with how mathematical operations (<code>impl<\/code>s of <code>core::ops::{Add, Sub, Mul, Div}<\/code> and others) are written and how that conflicts with rust&#8217;s orphan rule, which forbids you from implementing a trait for a foreign type (defined in a different crate).<\/p>\n<p class=\"info\">After publishing, I realized that this article heavily uses the LHS and RHS abbreviations, but never defines what they stand for! LHS is &#8220;left-hand side&#8221; and RHS is &#8220;right-hand side&#8221; and they denote (for a binary mathematical operation) what side of the operator a token is on. e.g. in <code>42 apples + 17 oranges<\/code>, the LHS is &#8220;42 apples,&#8221; while the RHS has a magnitude of 17 with a unit of &#8220;oranges.&#8221;<\/p>\n<p>Let&#8217;s take a look at how we would normally implement (in rust) support for a mathematical operation like <code>foo * bar<\/code> where <code>Foo<\/code> and <code>Bar<\/code> are different types, both local to the current crate:<\/p>\n<pre><code class=\"language-rust\">use core::ops::Mul;\r\n\r\nstruct Foo(i32);\r\nstruct Bar(i32);\r\n\r\n\/\/\/ Here, Bar is the RHS type and Foo is the LHS type, \r\n\/\/\/ i.e. this impl is used for `let prod = foo * bar`\r\nimpl Mul&lt;Bar&gt; for Foo {\r\n    type Output = i32;\r\n    \r\n    fn mul(self, other: Bar) -&gt; Self::Output {\r\n        self.0 * other.0\r\n    }\r\n}\r\n\r\n\/\/\/ Here, Foo is the RHS type and Bar is the LHS type, \r\n\/\/\/ i.e. this impl is used for `let prod = bar * foo`\r\nimpl Mul&lt;Foo&gt; for Bar {\r\n    type Output = i32;\r\n    \r\n    fn mul(self, other: Foo) -&gt; Self::Output {\r\n        self.0 * other.0\r\n    }\r\n}\r\n\r\nfn main() {\r\n    println!(\"{}\", Foo(7) * Bar(6));\r\n    println!(\"{}\", Bar(6) * Foo(7));\r\n}\r\n<\/code><\/pre>\n<p><em>You can <a href=\"https:\/\/play.rust-lang.org\/?version=stable&amp;mode=debug&amp;edition=2021&amp;gist=adb3840513d6166c59eabae744cb52c7\" rel=\"follow\">try this online<\/a> in the rust playground.<\/em><\/p>\n<p>The above code demonstrates the commutative property of scalar multiplication: <code>foo * bar<\/code> is the same as <code>bar * foo<\/code> and returns the same result, both in type (here, <code>i32<\/code>) and magnitude (<code>42<\/code> in this example).<\/p>\n<p>It&#8217;s important for a type system to allow (or even require) you to write out separate implementations for each of the two commutative permutations because not all mathematical operations (or even all multiplications) are commutative. For example, while addition is generally commutative, subtraction isn&#8217;t (<code>4 - 2<\/code> gives a different result from <code>2 - 4<\/code>) &#8211; and multiplication of matrices <code>M<\/code> and <code>N<\/code> may not only give different results for <code>M * N<\/code> as compared to <code>N * M<\/code>, one of those operations may be valid while the other is an error!<\/p>\n<p>So far, we haven&#8217;t run into any issues. But let&#8217;s say we have a bunch of different types, all of which (for reasons we won&#8217;t get into) can be boiled down to an integer equivalent and we want to support commutative multiplication for them all. It sounds like a textbook case for the use of generics: define a trait <code>AsInt<\/code>, have each type implement it however it likes, then implement <code>core::ops::Mul<\/code> via the <code>AsInt<\/code> trait:<\/p>\n<pre><code class=\"language-rust\">use core::ops::Mul;\r\n\r\ntrait AsInt {\r\n    fn as_int(&amp;self) -&gt; i32;\r\n}\r\n\r\nstruct Foo(i32);\r\nstruct Bar(i32);\r\n\r\nimpl AsInt for Foo {\r\n    fn as_int(&amp;self) -&gt; i32 { self.0 }\r\n}\r\n\r\nimpl AsInt for Bar {\r\n    fn as_int(&amp;self) -&gt; i32 { self.0 }\r\n}\r\n\r\nimpl&lt;Lhs: AsInt, Rhs: AsInt&gt; Mul&lt;Rhs&gt; for Lhs {\r\n    type Output = i32;\r\n    \r\n    fn mul(self, other: Rhs) -&gt; Self::Output {\r\n        self.as_int() * other.as_int()\r\n    }\r\n}\r\n<\/code><\/pre>\n<p><em>You can <a href=\"https:\/\/play.rust-lang.org\/?version=stable&amp;mode=debug&amp;edition=2021&amp;gist=662f7b93082ed25bb0e8be9338c10997\" rel=\"follow\">try this online<\/a> in the rust playground.<\/em><\/p>\n<p>Unfortunately, this doesn&#8217;t compile:<\/p>\n<pre class=\"language-rust_errors\" tabindex=\"0\"><code class=\"hG7gaiXUHSHwDNYhJsb8 GtOBiXh6TX6MRcPpfiv5 language-rust_errors\"><span class=\"token error\">error<a class=\"token error-explanation\" href=\"https:\/\/doc.rust-lang.org\/stable\/error-index.html#E0210\" target=\"_blank\" rel=\"noopener\">[E0210]<\/a>: type parameter `Lhs` must be used as the type parameter for some local type (e.g., `MyStruct&lt;Lhs&gt;`)<\/span>\r\n  <a class=\"token error-location\" href=\"https:\/\/play.rust-lang.org\/#\" data-line=\"18\" data-col=\"6\">--&gt; src\/main.rs:18:6\r\n<\/a>   |\r\n18 | impl&lt;Lhs: AsInt, Rhs: AsInt&gt; Mul&lt;Rhs&gt; for Lhs {\r\n   |      ^^^ type parameter `Lhs` must be used as the type parameter for some local type\r\n   |\r\n<span class=\"token note\">   = note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local<\/span><span class=\"token note\">\r\n   = note: only traits defined in the current crate can be implemented for a type parameter<\/span>\r\n\r\nFor more information about this error, try `rustc --explain E0210`.\r\n<span class=\"token error\">error: could not compile `playground` due to previous error<\/span><\/code><\/pre>\n<p>The problem is that while all the types and the traits in this implementation are indeed local, the rust compiler doesn&#8217;t check if we are in violation of the orphan rule by checking which types implement the (local) trait we are implementing (another) trait against &#8211; it just checks to see if the implementing type itself is local. You can actually use a generic parameter implementing any (foreign or local) trait in your <code>impl<\/code>, but you can&#8217;t implement against that generic type directly &#8211; you can only forward it as a generic parameter to a local type.<\/p>\n<h5>Sidebar: Quare rust&#8217;s orphan rule and its limitations?<\/h5>\n<p>The most succinct PLT answer to this is that it&#8217;s because &#8220;local types&#8221; is a closed set (a new local type can never be added without changing your code and its API) while &#8220;types implementing local trait&#8221; is (or could be) an open set: a downstream user of your crate\/library may implement your trait on their type at a later date, and suddenly we could have a conflict. You might be tempted to think &#8220;that&#8217;s on them,&#8221; and I wouldn&#8217;t blame you (and might even agree) but the problems don&#8217;t stop there &#8211; we absolutely need the ability to implement both local and foreign traits, but a crate or library <em>upstream<\/em> of yours (or even the standard library itself) might implement the same foreign trait against types implementing another foreign trait, and then the conflict would be your problem, in your code.<\/p>\n<p>Of course the rust compiler could be smarter about this and allow a combination of only certain permutations of impl\/for local\/foreign types\/traits to get around these restrictions (e.g. allow implementing anything for a local type, implementing only sealed traits inaccessible to downstream users for foreign types, etc) and while there are open issues and rfcs for some of these, the road to hell is paved with good intentions and there are a thousand pitfalls.<sup id=\"rf1-4901\"><a href=\"#fn1-4901\" title=\"For example, a foreign trait is implemented for impls of a local trait, but one of your types implements both the local trait and some other upstream trait and a later upstream release implements the same foreign trait for all impls of the other foreign trait, and suddenly your type has multiple impls for the same trait.\" rel=\"footnote\">1<\/a><\/sup> Long story short, the situation is what it is (for now) for $reasons and until that changes, these restrictions on commutative operations for generic types aren&#8217;t going anywhere.<\/p>\n<h5>Back to the issue at hand<\/h5>\n<p>You can kind of work around this by abusing <code>Deref<\/code> with an output that&#8217;s some intermediate type exposing a reference to an <code>i32<\/code> (because we actually have an underlying <code>i32<\/code> in this case, and are not just calculating one out of the blue each time), but <a href=\"https:\/\/web.mit.edu\/rust-lang_v1.25\/arch\/amd64_ubuntu1404\/share\/doc\/rust\/html\/book\/first-edition\/deref-coercions.html\" rel=\"follow\">deref coercion<\/a> will only get you so far.<\/p>\n<p>For the particular case of <code>Size<\/code>, we just need to implement commutative multiplication of <code>Size * number<\/code> and <code>number * Size<\/code> so it turns out we can actually side-step this entire debate by manually writing out a million or so different <code>impl<\/code>s, one for each primitive numeric type (macros help here!). Then multiply those by four, because you need to write a separate <code>impl<\/code> for each of <code>Foo * Bar<\/code>, <code>Foo * &amp;Bar<\/code>, <code>&amp;Foo * Bar<\/code> and <code>&amp;Foo * &amp;Bar<\/code>. Lots of code, but conceptually simple.<\/p>\n<p>Except it turns out not to be so simple after all. Here&#8217;s an example that demonstrates commutative multiplication of a type (but not a reference to a type) with an <code>i32<\/code>\u00a0value:<\/p>\n<pre><code class=\"language-rust\">use core::ops::Mul;\r\n\r\n#[derive(Debug, Copy, Clone)]\r\nstruct Foo(i32);\r\n\r\nimpl Mul&lt;Foo&gt; for i32 {\r\n    type Output = Foo;\r\n    \r\n    fn mul(self, other: Foo) -&gt; Self::Output {\r\n        Foo(self * other.0)\r\n    }\r\n}\r\n\r\nimpl Mul&lt;i32&gt; for Foo {\r\n    type Output = Foo;\r\n    \r\n    fn mul(self, other: i32) -&gt; Self::Output {\r\n        Foo(self.0 * other)\r\n    }\r\n}\r\n\r\nfn main() {\r\n    println!(\"{:?}\", Foo(7) * 6);\r\n    println!(\"{:?}\", 6 * Foo(7));\r\n}\r\n<\/code><\/pre>\n<p><em>You can <a href=\"https:\/\/play.rust-lang.org\/?version=stable&amp;mode=debug&amp;edition=2021&amp;gist=9f811c264b81a214c945669caff16beb\" rel=\"follow\">try this online<\/a> in the rust playground.<\/em><\/p>\n<p>It works great. This time we are returning a strongly-typed <code>Foo<\/code> rather than an <code>i32<\/code> scalar value, commutative multiplication works fine, the code compiles, and prints the expected output.<\/p>\n<p>We originally wanted to make this generic over all primitive numeric types, so that if the user has a <code>num: u8<\/code> or a <code>float: f64<\/code> lying around, they can just perform the multiplication automatically without getting a type mismatch error like you would with the above if you tried to multiply by some already-typed value that rust can&#8217;t coerce\/infer to be an <code>i32<\/code> (which our impl is specifically for):<\/p>\n<pre><code class=\"language-rust\">fn main() {\r\n    println!(\"{:?}\", Foo(7) * 6_u8);\r\n    println!(\"{:?}\", 6_f32 * Foo(7));\r\n}\r\n<\/code><\/pre>\n<p><em>You can <a href=\"https:\/\/play.rust-lang.org\/?version=stable&amp;mode=debug&amp;edition=2021&amp;gist=c577d1f4d4b6eda5466c73b3517123ba\" rel=\"follow\">try this online<\/a> in the rust playground.<\/em><\/p>\n<p>Which gives the following (expected) type errors:<\/p>\n<pre class=\"language-rust_errors\" tabindex=\"0\"><code class=\"hG7gaiXUHSHwDNYhJsb8 GtOBiXh6TX6MRcPpfiv5 language-rust_errors\"><span class=\"token error\">error<a class=\"token error-explanation\" href=\"https:\/\/doc.rust-lang.org\/stable\/error-index.html#E0308\" target=\"_blank\" rel=\"noopener\">[E0308]<\/a>: mismatched types<\/span>\r\n  <a class=\"token error-location\" href=\"https:\/\/play.rust-lang.org\/#\" data-line=\"23\" data-col=\"31\">--&gt; src\/main.rs:23:31\r\n<\/a>   |\r\n23 |     println!(\"{:?}\", Foo(7) * 6_u8);\r\n   |                               ^^^^ expected `i32`, found `u8`\r\n   |\r\n<span class=\"token rust-errors-help\">help: change the type of the numeric literal from `u8` to `i32`\r\n<\/span>   |\r\n23 |     println!(\"{:?}\", Foo(7) * 6_i32);\r\n   |                                 ~~~\r\n\r\n<span class=\"token error\">error<a class=\"token error-explanation\" href=\"https:\/\/doc.rust-lang.org\/stable\/error-index.html#E0277\" target=\"_blank\" rel=\"noopener\">[E0277]<\/a>: cannot multiply `f32` by `Foo`<\/span>\r\n  <a class=\"token error-location\" href=\"https:\/\/play.rust-lang.org\/#\" data-line=\"24\" data-col=\"28\">--&gt; src\/main.rs:24:28\r\n<\/a>   |\r\n24 |     println!(\"{:?}\", 6_f32 * Foo(7));\r\n   |                            ^ no implementation for `f32 * Foo`\r\n   |\r\n   = <span class=\"token rust-errors-help\">help: the trait `Mul&lt;Foo&gt;` is not implemented for `f32`\r\n<\/span>\r\nSome errors have detailed explanations: E0277, E0308.\r\nFor more information about an error, try `rustc --explain E0277`.<\/code><\/pre>\n<p>We said we can&#8217;t use generics to implement this support, but we can add a second pair of <code>impl Mul<\/code> for <code>u8<\/code> to get this to work, right?<\/p>\n<pre><code class=\"language-rust\">use core::ops::Mul;\r\n\r\n#[derive(Debug, Copy, Clone)]\r\nstruct Foo(i32);\r\n\r\nimpl Mul&lt;Foo&gt; for i32 {\r\n    type Output = Foo;\r\n    \r\n    fn mul(self, other: Foo) -&gt; Self::Output {\r\n        Foo(self * other.0)\r\n    }\r\n}\r\n\r\nimpl Mul&lt;i32&gt; for Foo {\r\n    type Output = Foo;\r\n    \r\n    fn mul(self, other: i32) -&gt; Self::Output {\r\n        Foo(self.0 * other)\r\n    }\r\n}\r\n\r\nimpl Mul&lt;Foo&gt; for u8 {\r\n    type Output = Foo;\r\n    \r\n    fn mul(self, other: Foo) -&gt; Self::Output {\r\n        Foo(self as i32 * other.0)\r\n    }\r\n}\r\n\r\nimpl Mul&lt;u8&gt; for Foo {\r\n    type Output = Foo;\r\n    \r\n    fn mul(self, other: u8) -&gt; Self::Output {\r\n        Foo(self.0 * other as i32)\r\n    }\r\n}\r\n\r\nfn main() {\r\n    println!(\"{:?}\", Foo(7) * 6_u8);\r\n    println!(\"{:?}\", 6_i32 * Foo(7));\r\n}\r\n<\/code><\/pre>\n<p><em>You can <a href=\"https:\/\/play.rust-lang.org\/?version=stable&amp;mode=debug&amp;edition=2021&amp;gist=2dc3779c47f9d411b9ecc3b2323b6aaa\" rel=\"follow\">try this online<\/a> in the rust playground.<\/em><\/p>\n<p>Indeed, this adds support for multiplying a <code>Foo<\/code> by a <code>u8<\/code> or the other way around, just as we wanted. We also still have support for multiplying <code>Foo<\/code> by <code>i32<\/code> (and vice-versa) as well. Great! This is what we wanted, right? Ergonomics +100 achievement unlocked!<\/p>\n<p>Unfortunately, no. While we\u00a0<em>did<\/em> add support for multiplying by <code>u8<\/code> or <code>i32<\/code> typed values, we broke something probably much more important: the ability to multiply by an untyped (or at least, not explicitly typed) literal:<\/p>\n<pre><code class=\"language-rust\">use core::ops::Mul;\r\n\r\n#[derive(Debug)]\r\nstruct Foo(i32);\r\n\r\nimpl Mul&lt;Foo&gt; for i32 {\r\n    type Output = Foo;\r\n\r\n    fn mul(self, other: Foo) -&gt; Self::Output {\r\n        Foo(self * other.0)\r\n    }\r\n}\r\n\r\nimpl Mul&lt;Foo&gt; for u8 {\r\n    type Output = Foo;\r\n    \r\n    fn mul(self, other: Foo) -&gt; Self::Output {\r\n        Foo(self as i32 * other.0)\r\n    }\r\n}\r\n\r\nfn main() {\r\n    let prod = 7 * Foo(6);\r\n    assert_eq!(prod.0, 42);\r\n}\r\n<\/code><\/pre>\n<p><em>You can <a href=\"https:\/\/play.rust-lang.org\/?version=nightly&amp;mode=debug&amp;edition=2021&amp;gist=0150f1c175f6e5af8c556f995788f0ad\" rel=\"follow\">try this online<\/a> in the rust playground.<\/em><\/p>\n<p>This breaks in a rather weird way: you&#8217;d expect that if there&#8217;s any confusion about what a type is, it&#8217;s about whether <code>7<\/code> is an <code>i32<\/code>\u00a0or <code>u8<\/code> here. Indeed, that&#8217;s what&#8217;s happening internally, but that&#8217;s not what the error surfaced by the rust compiler says:<\/p>\n<pre class=\"language-rust_errors\" tabindex=\"0\"><code class=\"hG7gaiXUHSHwDNYhJsb8 GtOBiXh6TX6MRcPpfiv5 language-rust_errors\"><span class=\"token error\">error<a class=\"token error-explanation\" href=\"https:\/\/doc.rust-lang.org\/nightly\/error-index.html#E0282\" target=\"_blank\" rel=\"noopener\">[E0282]<\/a>: type annotations needed<\/span>\r\n  <a class=\"token error-location\" href=\"https:\/\/play.rust-lang.org\/?version=nightly&amp;mode=debug&amp;edition=2021#\" data-line=\"39\" data-col=\"9\">--&gt; src\/main.rs:39:9\r\n<\/a>   |\r\n39 |     let prod = 7 * Foo(6);\r\n   |         ^^^^^\r\n   |\r\n<span class=\"token note\">   = note: type must be known at this point<\/span>\r\n<span class=\"token rust-errors-help\">help: consider giving `prod` an explicit type\r\n<\/span>   |\r\n39 |     let prod: _ = 7 * Foo(6);\r\n   |              +++\r\n<\/code><\/pre>\n<p>Weird. We know (or at least, can reasonably surmise) the problem is with the ambiguity in the literal <code>7<\/code> and whether the compiler should invoke the <code>Mul&lt;Foo&gt; for i32<\/code> impl or the <code>Mul&lt;Foo&gt; for u8<\/code> impl, but the compiler says the problem is actually with the missing return type for the entire operation (which is always <code>Foo<\/code> because that&#8217;s the <code>Mul::Output<\/code> we have specified for both)! In fact, an older version of the compiler <a href=\"https:\/\/github.com\/rust-lang\/rust\/issues\/98404\" rel=\"nofollow\">produces a better message<\/a>:<\/p>\n<pre class=\"language-rust_errors\" tabindex=\"0\"><code class=\"hG7gaiXUHSHwDNYhJsb8 GtOBiXh6TX6MRcPpfiv5 language-rust_errors\">&lt;snip&gt;\r\n\r\n<span class=\"token error\">error<a class=\"token error-explanation\" href=\"https:\/\/doc.rust-lang.org\/stable\/error-index.html#E0283\" target=\"_blank\" rel=\"noopener\">[E0283]<\/a>: type annotations needed<\/span>\r\n  <a class=\"token error-location\" href=\"https:\/\/play.rust-lang.org\/?version=stable&amp;mode=debug&amp;edition=2021#\" data-line=\"39\" data-col=\"19\">--&gt; src\/main.rs:39:19\r\n<\/a>   |\r\n39 |     let prod1 = 7 * Foo(6);\r\n   |                   ^ cannot infer type for type `{integer}`\r\n   |\r\nnote: multiple `impl`s satisfying `{integer}: Mul&lt;Foo&gt;` found\r\n  <a class=\"token error-location\" href=\"https:\/\/play.rust-lang.org\/?version=stable&amp;mode=debug&amp;edition=2021#\" data-line=\"22\" data-col=\"1\">--&gt; src\/main.rs:22:1\r\n<\/a>   |\r\n22 | impl Mul&lt;Foo&gt; for i32 {\r\n   | ^^^^^^^^^^^^^^^^^^^^^\r\n...\r\n30 | impl Mul&lt;Foo&gt; for u8 {\r\n   | ^^^^^^^^^^^^^^^^^^^^\r\n<\/code><\/pre>\n<p>However, let&#8217;s just take the latest <code>rustc<\/code> at its word and add the missing <code>Foo<\/code> type to the <code>let prod = ...<\/code> expression:<\/p>\n<pre><code class=\"language-rust\">fn main() {\r\n    let prod: Foo = 7 * Foo(6);\r\n    assert_eq!(prod.0, 42);\r\n}\r\n<\/code><\/pre>\n<p><a href=\"https:\/\/github.com\/rust-lang\/rust\/issues\/98357\" rel=\"nofollow\">And everything magically works<\/a>! But we didn&#8217;t actually\u00a0<em>solve<\/em> the problem we were dealing with, we just worked around the resulting compiler error &#8211; something each of our users would have to do any time they relied on type inference to multiply a scalar number by a typed <code>Foo<\/code> (interesting tidbit: this doesn&#8217;t happen the other way around, when multiplying a <code>Foo<\/code> by a scalar value &#8211; I&#8217;m not sure why, but I&#8217;ve opened bugs for these issues: [<a href=\"https:\/\/github.com\/rust-lang\/rust\/issues\/98357\" rel=\"nofollow\">1<\/a>], [<a href=\"https:\/\/github.com\/rust-lang\/rust\/issues\/98404\" rel=\"nofollow\">2<\/a>]).<\/p>\n<p>To recap:<\/p>\n<ul>\n<li>Rust&#8217;s orphan rule prevents us from implementing commutative addition\/multiplication for types implementing a trait, which isn&#8217;t a complete blocker if you&#8217;re the only one that&#8217;s ever going to be implementing them because you can use macros or good, old copy-and-paste to work around that limitation and implement the operation manually. Half the operations can be generic over RHS (because <code>impl&lt;Rhs: ...&gt; Mul&lt;Rhs&gt; for SpecificType<\/code> is perfectly legal) but the other half need to be manually spelled out. If you&#8217;re doing just two or three types, it&#8217;s fairly manageable but since you need <code>4 * M * N<\/code> impls in total (accounting for the ref\/non-ref permutations), it can quickly spiral into insanity.<\/li>\n<li>A (temporary?) bug? quirk? limitation? in the rust compiler stops us from manually implementing commutative operations with the various numeric literals, because even though rust has a (silent) integer inference preference for <code>i32<\/code> and a default floating point type of <code>f64<\/code>, the presence of multiple impls breaks type inference in interesting ways.<\/li>\n<\/ul>\n<blockquote class=\"twitter-tweet\">\n<p dir=\"ltr\" lang=\"en\">Just published a long article on a weakness in the <a href=\"https:\/\/twitter.com\/hashtag\/rust?src=hash&amp;ref_src=twsrc%5Etfw\" rel=\"follow\">#rust<\/a> type system when it comes to commutative operations and how to preserve backwards compatibility even when making breaking changes.<a href=\"https:\/\/t.co\/Wodf9rfEF2\" rel=\"follow\">https:\/\/t.co\/Wodf9rfEF2<\/a><\/p>\n<p>\u2014 Mahmoud Al-Qudsi (@mqudsi) <a href=\"https:\/\/twitter.com\/mqudsi\/status\/1539751705331597312?ref_src=twsrc%5Etfw\" rel=\"follow\">June 22, 2022<\/a><\/p><\/blockquote>\n<p><script async src=\"https:\/\/platform.twitter.com\/widgets.js\" charset=\"utf-8\"><\/script><\/p>\n<h4>A new (and a newer) <code>size<\/code> crate<\/h4>\n<p>This brings us at long last to today&#8217;s announcement regarding a new <code>size<\/code> crate. Faced with the issues above while attempting to implement commutative mathematical operations, <code>size<\/code> now features support for the following, implemented via a combination of macros\/copy-and-paste and generic impls where possible:<\/p>\n<ul>\n<li>Strongly-typed addition and subtraction of <code>Size<\/code> values, giving <code>Size<\/code> results . This was implemented directly, as there&#8217;s only one type involved, with copy-and-pasted impls for the ref\/non-ref cases.<\/li>\n<li>Multiplication and division of an LHS <code>Size<\/code> by an RHS integer or floating value, yielding a <code>Size<\/code> instance; implemented via generics as <code>impl&lt;T: ..&gt; Mul&lt;T&gt; for Size<\/code> is perfectly accepted, then copy-and-pasted as needed to handle ref\/non-ref permutations.<\/li>\n<li>Multiplication of an LHS integer or floating point value by an RHS <code>Size<\/code> value, yielding a <code>Size<\/code> result.This could only be implemented for one integer type (<code>i64<\/code>) and floating type (<code>f64<\/code>) to prevent the bizarre breakage when an untyped integer\/float value is used (with only one possible <code>{float}<\/code> type and one possible <code>{integer}<\/code> type, rustc will try to coerce to the matching type of the two automatically). This had to be implemented manually (via macros) as rust&#8217;s orphan rule got in the way.<\/li>\n<li>Division of an LHS integer by a <code>Size<\/code> value is\u00a0<em>not<\/em> implemented, since it makes no sense (what does <code>42 \/ 16 KiB<\/code> yield?).<\/li>\n<\/ul>\n<p>That was pretty much it in terms of the features I&#8217;d wanted to implement from a few years back before I was stymied by the rust restrictions\/limitations we&#8217;ve discussed. But the additions and improvements to <code>size<\/code> didn&#8217;t stop there:<\/p>\n<ul>\n<li>As a result of implementing <code>core::ops::Subtraction<\/code>, it became necessary to add support for the concept of negative file sizes (something which can only exist in the abstract and wasn&#8217;t previously supported). This necessitated a change in the &#8220;output type&#8221; used by the library, and now the core primitive type returned\/expected by the library (generic overloads excepted) is <code>i64<\/code> rather than <code>u64<\/code>.<\/li>\n<li>The goal of this crate has changed from &#8220;merely&#8221; providing formatting for file sizes to encapsulating all operations on sizes in general by providing a strongly-typed size that can expose just the right number of features and functionality while restricting the user from doing things that don&#8217;t make sense (such as dividing a scalar integer by a file size, as mentioned above). To that end, it is now possible to directly compare <code>Size<\/code> types for equality or order (via <code>PartialEq<\/code> and <code>PartialOrd<\/code> impls).<\/li>\n<li>With its newfound ability to do more than just format file sizes for human readable output, it&#8217;s possible to imagine using <code>size<\/code> in completely different contexts. To that end, the <code>size<\/code> crate may now be compiled as a <code>no_std<\/code> library<sup id=\"rf2-4901\"><a href=\"#fn2-4901\" title=\"Just compile with default features disabled.\" rel=\"footnote\">2<\/a><\/sup> which lets you use the basic <code>Size<\/code> features such as initializing a <code>Size<\/code> from different units, comparing <code>Size<\/code> instances, etc\u00a0<a href=\"https:\/\/docs.rs\/size\/0.3.0\/size\/#crate-features\" rel=\"nofollow\">but disables features<\/a> that aren&#8217;t meant to be used in embedded or other <code>no_std<\/code> contexts.<\/li>\n<li>The <code>size<\/code> crate no longer has any dependencies. It previously featured only a single dependency on <code>num_crates<\/code> (plus its transitive dependency on <code>autocfg<\/code>) for abstracting over the different primitive numeric types, but the latest releases now use a sealed local trait and some macros to accomplish the same but without any foreign dependencies. Compilation time has been significantly improved as a result.<\/li>\n<\/ul>\n<p>The changes above formed the bulk <a href=\"https:\/\/docs.rs\/size\/0.2.1\/size\/index.html\" rel=\"nofollow\">of the <code>size<\/code> 0.2.0 release<\/a>. But just as I was about to sit down and write up this article, it struck me that the <code>Size<\/code> api was not very rusty. The crate (and its basic API) was originally written in 2018 and envisioned somewhat differently from how it turned out. The original idea was to take advantage of rust&#8217;s game-changing tagged enums to provide, in addition to pretty printing of file sizes, an interface for converting directly between sizes of different units (almost like a <code><a href=\"https:\/\/linux.die.net\/man\/1\/units\">units(1)<\/a><\/code> but in rust).<\/p>\n<p>Rust&#8217;s enums seemed perfect for the job, so <code>size<\/code> 0.1.x and 0.2.x shipped with an API that exposed an enum composed of a strongly-typed unit name and a generic, numeric size, e.g. <code>Size::Bytes(T)<\/code>, <code>Size::Gigabytes(T)<\/code>, <code>Size::Kibibytes(T)<\/code>, etc. But in practice, people aren&#8217;t reaching out for the <code>size<\/code> crate to convert between well-defined base-2\/base-10 size units, they&#8217;re using it to create strongly-typed <code>Size<\/code> objects to represent an underlying file size and format it for display. Users requested the ability to perform math\/logic on <code>Size<\/code> types, but users didn&#8217;t care for requesting an equivalent <code>Size<\/code> but with a base unit of gigabytes.<\/p>\n<p>The majority of the code I found on GitHub and in other nooks and crannies of the internet ended up looking like this:<\/p>\n<pre><code class=\"language-rust\">let size = Size::Bytes(some_value);\r\n...\r\nprintln!(\"File size: {}\", size);\r\n<\/code><\/pre>\n<p>Which, while being perfectly valid rust code and actually conforming to the rust formatting rules and regulations, just didn&#8217;t feel rusty and didn&#8217;t match the approach that other crates have pretty much clustered around. You don&#8217;t get the feeling that <code>Size&lt;T&gt;::Bytes()<\/code> is an enum so much as it appears to be an unfortunately mis-capitalized method exposed by the <code>Size<\/code> type. What&#8217;s more, the interface was extremely generic heavy, but the generics were only skin-deep because all operations coming out of a <code>Size<\/code> stripped the original <code>T<\/code> and returned values of the intermediate (<code>u64<\/code>\/<code>i64<\/code>) numeric type instead. While the <code>Size<\/code> variants were storing the user-picked <code>T<\/code>, it wasn&#8217;t actually\u00a0<em>used<\/em> anywhere except as an input to internal calculations completely masked to the end user, it wasn&#8217;t intuitive that operations like <code>Size&lt;u8&gt; + Size&lt;f64&gt;<\/code> were even possible let alone yielded a <code>Size&lt;i64&gt;<\/code> (regardless of the initial types), and the internal type conversions and changes to precision (one way or the other) were not intuitively exposed.<\/p>\n<p>Enter <code>size<\/code> 0.3.x with a new and rustier (if not improved) API that should feel more natural to rustaceans around the globe. <code>Size<\/code> has been changed from an <code>enum<\/code> to a <code>struct<\/code> and now exposes functions to model the behavior previously exposed by the old variants. The biggest API change is that the <code>Size<\/code> type itself is no longer generic\u00a0 and things like <code>Size::Kilobytes(10)<\/code> are now expressed as <code>Size::from_kilobytes(10)<\/code> (or, optionally, <code>Size::from_kb(10)<\/code> instead). It should be more immediately intuited that a numeric conversion is (or at least may be) taking place given the &#8220;from&#8221; in the function name and the fact that you are not directly instantiating an instance of <code>Size<\/code> containing a particular numeric type <code>T<\/code> that is somehow never afterwards seen.<\/p>\n<p>One other minor change that may be of interest to other crate developers: there are certain spellings or phrasings specific to each community, and it helps the ecosystem considerably for crate authors to make a conscious effort to adhere to them where possible. For example, while <code>size<\/code> 0.2.x spelled &#8220;lower case&#8221; as two words, the rust standard library has it as a single word &#8220;lowercase&#8221; and so enum members like <code>Style::FullLowerCase<\/code> have been renamed to <code>Style::FullLowercase<\/code> to match.<\/p>\n<h4>Preserving backwards compatibility in rust<\/h4>\n<p>It would seem like a massively breaking change to switch the core <code>Size<\/code> type from an <code>enum<\/code> to a <code>struct<\/code>, let alone to rename virtually all the interface members in such a manner. But if you take the unix &#8220;source-compatible&#8221; approach rather than focusing on strict ABI compatibility, things are actually not that &#8211; if you&#8217;re willing to break some conventions and bend the rust compiler to your will with liberal usage of <code>#[allow(...)]<\/code> in carefully chosen places.<\/p>\n<p>After the new <code>Size<\/code> interface was in place, a second <code>impl Size { ... }<\/code> was added, &#8211; this time prefixed with <code>#[doc(hidden)]<\/code> to keep it out of the documentation &#8211; that contained a number of &#8220;fakes,&#8221; in this case, const functions masquerading as enum variants. Here&#8217;s an excerpt of what that looks like:<\/p>\n<pre><code class=\"language-rust\">#[doc(hidden)]\r\nimpl Size {\r\n    #![allow(non_snake_case)]\r\n\r\n    #[inline]\r\n    #[deprecated(since = \"0.3\", note = \"Use Size::from_bytes() instead\")]\r\n    \/\/\/ Express a size in bytes.\r\n    pub const fn Bytes(t: T) -&gt; Self { Self::from_bytes(t) }\r\n\r\n    #[inline]\r\n    #[deprecated(since = \"0.3\", note = \"Use Size::from_kibibytes() instead\")]\r\n    \/\/\/ Express a size in kibibytes. Actual size is 2^10 \\* the value.\r\n    pub const fn Kibibytes(t: T) -&gt; Self { Self::from_kibibytes(t) }\r\n\r\n    \/\/ ...\r\n}\r\n<\/code><\/pre>\n<p>You may not be able to achieve full compatibility with the old API and there are a lot of cases where this won&#8217;t cut it, but fortunately for us, they&#8217;re not how most users would approach things. For example, someone creating a <code>Size<\/code> via <code>Size&lt;u64&gt;::Bytes(num.into())<\/code> would find that their code no longer compiles, as <code>Size<\/code> itself is not generic (rather, it&#8217;s the function\/mock variant <code>Size::Bytes&lt;T&gt;<\/code> that is generic over <code>T<\/code>). But luckily for us, that&#8217;s not how most people would write that code and the &#8220;natural&#8221; way of expressing it (<code>Size::Bytes(num as u64)<\/code>) continues to compile, happily oblivious to the fact that we&#8217;re actually calling a function called <code>Bytes()<\/code> rather than constructing an enum variant <code>Size&lt;T&gt;::Bytes<\/code>.<\/p>\n<p>For the renamed &#8220;plain&#8221; enums, a similar approach was used to make it seem like <code>FullLowerCase<\/code> was still a valid member of the <code>Style<\/code> enum (used to specify how the unit name is formatted when the size is pretty-printed):<\/p>\n<pre><code class=\"language-rust\">enum Style { .... }\r\nimpl Style {\r\n    #[doc(hidden)]\r\n    #[allow(non_upper_case_globals)]\r\n    #[deprecated(since = \"0.3\", note = \"Use Style::FullLowercase instead\")]\r\n    \/\/\/ A backwards-compatible alias for [`Style::FullLowercase`]\r\n    pub const FullLowerCase: Style = Style::FullLowercase;\r\n}\r\n<\/code><\/pre>\n<p>In this particular case, it would have been possible to keep the old <code>FullLowerCase<\/code> enum member around and simply hide it from the docs, since <code>Style<\/code> remained an <code>enum<\/code>. But that would mean updating all our match sites to handle both the old and the new name, incurring both a maintenance and a (negligible) runtime cost to keeping the backwards-compatible name around. With this approach, and especially with all the old names kept in a separate <code>impl Style<\/code> block that only contained shims for the deprecated API, there is almost no cost to keeping the code compatible for a few versions or however long we choose to support the legacy API.<\/p>\n<p>Again, this isn&#8217;t a magic fix that keeps\u00a0<em>everything<\/em> working, but it does handle pretty much all the cases our users were actually using (in this case, calling a function and specifying a <code>Style::Foo<\/code> variant as a parameter). I highly recommend using GitHub&#8217;s (or any other service&#8217;s) code search feature to look at how people are using your API before introducing breaking changes or remodeling an API; it really helps to understand how your users approach your crate, which may be quite different than how you originally intended for it to be used.<\/p>\n<h4>Using <code>size<\/code> or contributing<\/h4>\n<p>The latest release of the <code>size<\/code> crate <a href=\"https:\/\/crates.io\/crates\/size\" rel=\"nofollow\">is available on crates.io<\/a>, and <a href=\"https:\/\/docs.rs\/size\/latest\/size\/\" rel=\"nofollow\">the documentation<\/a> has been completely overhauled as part of the new 0.2.x and 0.3.x releases. The source code <a href=\"https:\/\/github.com\/neosmart\/prettysize-rs\" rel=\"nofollow\">is available on GitHub<\/a> and is released under the MIT license.<\/p>\n<p class=\"info\">I&#8217;ve actually released <code>size<\/code> 0.4 shortly after publishing this article, mainly to future-proof the API against breaking changes in the future (by breaking it in the here-and-now instead \ud83e\udd26\u200d\u2640\ufe0f). Unfortunately, <a href=\"https:\/\/old.reddit.com\/r\/rust\/comments\/vj4wfe\/a_primer_on_limitations_in_rusts_support_for\/idh19t5\/\" rel=\"follow\">I wasn&#8217;t able to use any of the methods outlined above<\/a> to preserve backwards compatibility, and I humbly apologize to everyone affected by this breakage!<\/p>\n<p>You can use <code>size<\/code> in your rust code today by simply adding a reference to <code>size<\/code> in your <code>Cargo.toml<\/code> and placing <code>use size::Size<\/code> at the top of your rust code:<\/p>\n<pre><code class=\"language-rust\">use size::Size;\r\nuse std::fs::File;\r\n\r\nfn main() {\r\n    let metadata = File::open(\"foo.bin\").metadata().unwrap();\r\n    let file_size = Size::from_bytes(metadata.len());\r\n    println!(\"{}\", file_size); \/\/ prints \"13.37 MiB\"\r\n}\r\n<\/code><\/pre>\n<h4>Sign up and follow for more!<\/h4>\n<p>If you found this article interesting, please <a href=\"https:\/\/twitter.com\/mqudsi\" rel=\"follow\">follow me on twitter<\/a> and sign up for my rust mailing list to get notifications on new rust articles and make sure you never miss out. You won&#8217;t get any other emails, I pinky swear!<\/p>\n<div class=\"sendy_widget\" style='margin-bottom: 0.5em;'>\n<p><em>If you would like to receive a notification the next time we release a rust library, publish a crate, or post some rust-related developer articles, you can subscribe below. Note that you'll only get notifications relevant to rust programming and development by NeoSmart Technologies. If you want to receive email updates for all NeoSmart Technologies posts and releases, please sign up in the sidebar to the right instead.<\/em><\/p>\n<iframe tabIndex=-1 onfocus=\"sendy_no_focus\" src=\"https:\/\/neosmart.net\/sendy\/subscription?f=BUopX8f2VyLSOb892VIx6W4IUNylMrro5AN6cExmwnoKFQPz9892VSk4Que8yv892RnQgL&title=Join+the+rust+mailing+list\" style=\"height: 300px; width: 100%;\"><\/iframe>\n<\/div>\n<script type=\"text\/javascript\">function sendy_no_focus(e) { e.preventDefault(); }<\/script>\n<blockquote class=\"twitter-tweet\">\n<p dir=\"ltr\" lang=\"en\">Just published a long article on a weakness in the <a href=\"https:\/\/twitter.com\/hashtag\/rust?src=hash&amp;ref_src=twsrc%5Etfw\" rel=\"follow\">#rust<\/a> type system when it comes to commutative operations and how to preserve backwards compatibility even when making breaking changes.<a href=\"https:\/\/t.co\/Wodf9rfEF2\" rel=\"follow\">https:\/\/t.co\/Wodf9rfEF2<\/a><\/p>\n<p>\u2014 Mahmoud Al-Qudsi (@mqudsi) <a href=\"https:\/\/twitter.com\/mqudsi\/status\/1539751705331597312?ref_src=twsrc%5Etfw\" rel=\"follow\">June 22, 2022<\/a><\/p><\/blockquote>\n<p><script async src=\"https:\/\/platform.twitter.com\/widgets.js\" charset=\"utf-8\"><\/script><\/p>\n<hr class=\"footnotes\"><ol class=\"footnotes\"><li id=\"fn1-4901\"><p>For example, a foreign trait is implemented for impls of a local trait, but one of your types implements both the local trait and some other upstream trait and a later upstream release implements the same foreign trait for all impls of the other foreign trait, and suddenly your type has multiple impls for the same trait.&nbsp;<a href=\"#rf1-4901\" class=\"backlink\" title=\"Jump back to footnote 1 in the text.\">&#8617;<\/a><\/p><\/li><li id=\"fn2-4901\"><p>Just compile with default features disabled.&nbsp;<a href=\"#rf2-4901\" class=\"backlink\" title=\"Jump back to footnote 2 in the text.\">&#8617;<\/a><\/p><\/li><\/ol>","protected":false},"excerpt":{"rendered":"<p>Rust&#8217;s weakness in commutative operations, how to preserve backwards compatibility with breaking API changes in rust, and a new PrettySize crate release. <a href=\"https:\/\/neosmart.net\/blog\/prettysize-0-3-release-and-a-weakness-in-rusts-type-system\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":505,"featured_media":4911,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[999],"tags":[1021,964,11,1022,936,1020],"class_list":["post-4901","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-programming","tag-backwards-compatibility","tag-prettysize","tag-programming","tag-release","tag-rust","tag-size"],"aioseo_notices":[],"jetpack_featured_media_url":"https:\/\/neosmart.net\/blog\/wp-content\/uploads\/2022\/06\/PrettySize-0.3-Featured-Image.png","jetpack_shortlink":"https:\/\/wp.me\/p4xDa-1h3","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/posts\/4901","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/users\/505"}],"replies":[{"embeddable":true,"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/comments?post=4901"}],"version-history":[{"count":22,"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/posts\/4901\/revisions"}],"predecessor-version":[{"id":4924,"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/posts\/4901\/revisions\/4924"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/media\/4911"}],"wp:attachment":[{"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/media?parent=4901"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/categories?post=4901"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/neosmart.net\/blog\/wp-json\/wp\/v2\/tags?post=4901"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}