[INFO] cloning repository https://github.com/pat11639/fake [INFO] running `Command { std: "git" "-c" "credential.helper=" "-c" "credential.helper=/workspace/cargo-home/bin/git-credential-null" "clone" "--bare" "https://github.com/pat11639/fake" "/workspace/cache/git-repos/https%3A%2F%2Fgithub.com%2Fpat11639%2Ffake", kill_on_drop: false }` [INFO] [stderr] Cloning into bare repository '/workspace/cache/git-repos/https%3A%2F%2Fgithub.com%2Fpat11639%2Ffake'... [INFO] running `Command { std: "git" "rev-parse" "HEAD", kill_on_drop: false }` [INFO] [stdout] a6a7d443555d4daaf58fcdd52bd9371786f7bc06 [INFO] testing pat11639/fake against master#61469b682c2b0bf9cebc4622f1859e2bb3b7ebca for pr-91743 [INFO] running `Command { std: "git" "clone" "/workspace/cache/git-repos/https%3A%2F%2Fgithub.com%2Fpat11639%2Ffake" "/workspace/builds/worker-113/source", kill_on_drop: false }` [INFO] [stderr] Cloning into '/workspace/builds/worker-113/source'... [INFO] [stderr] done. [INFO] validating manifest of git repo https://github.com/pat11639/fake on toolchain 61469b682c2b0bf9cebc4622f1859e2bb3b7ebca [INFO] running `Command { std: "/workspace/cargo-home/bin/cargo" "+61469b682c2b0bf9cebc4622f1859e2bb3b7ebca" "metadata" "--manifest-path" "Cargo.toml" "--no-deps", kill_on_drop: false }` [INFO] started tweaking git repo https://github.com/pat11639/fake [INFO] finished tweaking git repo https://github.com/pat11639/fake [INFO] tweaked toml for git repo https://github.com/pat11639/fake written to /workspace/builds/worker-113/source/Cargo.toml [INFO] crate git repo https://github.com/pat11639/fake already has a lockfile, it will not be regenerated [INFO] running `Command { std: "/workspace/cargo-home/bin/cargo" "+61469b682c2b0bf9cebc4622f1859e2bb3b7ebca" "fetch" "--manifest-path" "Cargo.toml", kill_on_drop: false }` [INFO] [stderr] Blocking waiting for file lock on package cache [INFO] [stderr] Blocking waiting for file lock on package cache [INFO] [stderr] Downloading crates ... [INFO] [stderr] Downloaded line-wrap v0.1.1 [INFO] [stderr] Downloaded pulldown-cmark v0.8.0 [INFO] [stderr] Downloaded clang-sys v1.2.0 [INFO] [stderr] Downloaded getopts v0.2.21 [INFO] [stderr] Downloaded ordinal v0.2.3 [INFO] [stderr] Downloaded maud_htmlescape v0.17.0 [INFO] [stderr] Downloaded flate2 v1.0.20 [INFO] [stderr] Downloaded maud_macros v0.22.2 [INFO] [stderr] Downloaded maud v0.22.2 [INFO] [stderr] Downloaded onig_sys v69.7.0 [INFO] [stderr] Downloaded cc v1.0.69 [INFO] [stderr] Downloaded plist v0.4.2 [INFO] [stderr] Downloaded onig v5.0.0 [INFO] [stderr] Downloaded syntect v3.3.0 [INFO] running `Command { std: "docker" "create" "-v" "/var/lib/crater-agent-workspace/builds/worker-113/target:/opt/rustwide/target:rw,Z" "-v" "/var/lib/crater-agent-workspace/builds/worker-113/source:/opt/rustwide/workdir:ro,Z" "-v" "/var/lib/crater-agent-workspace/cargo-home:/opt/rustwide/cargo-home:ro,Z" "-v" "/var/lib/crater-agent-workspace/rustup-home:/opt/rustwide/rustup-home:ro,Z" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-w" "/opt/rustwide/workdir" "-m" "1610612736" "--user" "0:0" "--network" "none" "ghcr.io/rust-lang/crates-build-env/linux@sha256:d190cb871061d98bc6d0581d85cb2ecb09a0f8a142ba5463de30be9999fc3251" "/opt/rustwide/cargo-home/bin/cargo" "+61469b682c2b0bf9cebc4622f1859e2bb3b7ebca" "metadata" "--no-deps" "--format-version=1", kill_on_drop: false }` [INFO] [stdout] 475ed816d37b6fd285a08ec07f1c5ba932d62d96c92a0f1e18fe1cb92c04bf3a [INFO] running `Command { std: "docker" "start" "-a" "475ed816d37b6fd285a08ec07f1c5ba932d62d96c92a0f1e18fe1cb92c04bf3a", kill_on_drop: false }` [INFO] running `Command { std: "docker" "inspect" "475ed816d37b6fd285a08ec07f1c5ba932d62d96c92a0f1e18fe1cb92c04bf3a", kill_on_drop: false }` [INFO] running `Command { std: "docker" "rm" "-f" "475ed816d37b6fd285a08ec07f1c5ba932d62d96c92a0f1e18fe1cb92c04bf3a", kill_on_drop: false }` [INFO] [stdout] 475ed816d37b6fd285a08ec07f1c5ba932d62d96c92a0f1e18fe1cb92c04bf3a [INFO] running `Command { std: "docker" "create" "-v" "/var/lib/crater-agent-workspace/builds/worker-113/target:/opt/rustwide/target:rw,Z" "-v" "/var/lib/crater-agent-workspace/builds/worker-113/source:/opt/rustwide/workdir:ro,Z" "-v" "/var/lib/crater-agent-workspace/cargo-home:/opt/rustwide/cargo-home:ro,Z" "-v" "/var/lib/crater-agent-workspace/rustup-home:/opt/rustwide/rustup-home:ro,Z" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-e" "CARGO_INCREMENTAL=0" "-e" "RUST_BACKTRACE=full" "-e" "RUSTFLAGS=--cap-lints=forbid" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-w" "/opt/rustwide/workdir" "-m" "1610612736" "--user" "0:0" "--network" "none" "ghcr.io/rust-lang/crates-build-env/linux@sha256:d190cb871061d98bc6d0581d85cb2ecb09a0f8a142ba5463de30be9999fc3251" "/opt/rustwide/cargo-home/bin/cargo" "+61469b682c2b0bf9cebc4622f1859e2bb3b7ebca" "build" "--frozen" "--message-format=json", kill_on_drop: false }` [INFO] [stdout] b0ff36933380a2ebf21a8f8054ba8b1303d4029a8fdd7bfe8bfe8bf7691d0973 [INFO] running `Command { std: "docker" "start" "-a" "b0ff36933380a2ebf21a8f8054ba8b1303d4029a8fdd7bfe8bfe8bf7691d0973", kill_on_drop: false }` [INFO] [stderr] Blocking waiting for file lock on package cache [INFO] [stderr] Compiling version_check v0.9.3 [INFO] [stderr] Compiling libc v0.2.98 [INFO] [stderr] Compiling proc-macro2 v1.0.27 [INFO] [stderr] Compiling memchr v2.4.0 [INFO] [stderr] Compiling unicode-xid v0.2.2 [INFO] [stderr] Compiling autocfg v1.0.1 [INFO] [stderr] Compiling cfg-if v1.0.0 [INFO] [stderr] Compiling glob v0.3.0 [INFO] [stderr] Compiling unicode-width v0.1.8 [INFO] [stderr] Compiling bitflags v1.2.1 [INFO] [stderr] Compiling log v0.4.14 [INFO] [stderr] Compiling regex-syntax v0.6.25 [INFO] [stderr] Compiling bindgen v0.56.0 [INFO] [stderr] Compiling humantime v2.1.0 [INFO] [stderr] Compiling termcolor v1.1.2 [INFO] [stderr] Compiling ansi_term v0.11.0 [INFO] [stderr] Compiling vec_map v0.8.2 [INFO] [stderr] Compiling strsim v0.8.0 [INFO] [stderr] Compiling syn v1.0.73 [INFO] [stderr] Compiling shlex v0.1.1 [INFO] [stderr] Compiling lazycell v1.3.0 [INFO] [stderr] Compiling lazy_static v1.4.0 [INFO] [stderr] Compiling serde v1.0.126 [INFO] [stderr] Compiling rustc-hash v1.1.0 [INFO] [stderr] Compiling peeking_take_while v0.1.2 [INFO] [stderr] Compiling pkg-config v0.3.19 [INFO] [stderr] Compiling cc v1.0.69 [INFO] [stderr] Compiling ryu v1.0.5 [INFO] [stderr] Compiling crc32fast v1.2.1 [INFO] [stderr] Compiling byteorder v1.4.3 [INFO] [stderr] Compiling quick-error v1.2.3 [INFO] [stderr] Compiling safemem v0.3.3 [INFO] [stderr] Compiling serde_json v1.0.64 [INFO] [stderr] Compiling serde_derive v1.0.126 [INFO] [stderr] Compiling adler v1.0.2 [INFO] [stderr] Compiling maud_htmlescape v0.17.0 [INFO] [stderr] Compiling xml-rs v0.8.3 [INFO] [stderr] Compiling linked-hash-map v0.5.4 [INFO] [stderr] Compiling same-file v1.0.6 [INFO] [stderr] Compiling itoa v0.4.7 [INFO] [stderr] Compiling pulldown-cmark v0.8.0 [INFO] [stderr] Compiling fnv v1.0.7 [INFO] [stderr] Compiling humantime v1.3.0 [INFO] [stderr] Compiling num-traits v0.2.14 [INFO] [stderr] Compiling num-integer v0.1.44 [INFO] [stderr] Compiling miniz_oxide v0.4.4 [INFO] [stderr] Compiling nom v5.1.2 [INFO] [stderr] Compiling proc-macro-error-attr v1.0.4 [INFO] [stderr] Compiling proc-macro-error v1.0.4 [INFO] [stderr] Compiling unicase v2.6.0 [INFO] [stderr] Compiling textwrap v0.11.0 [INFO] [stderr] Compiling getopts v0.2.21 [INFO] [stderr] Compiling line-wrap v0.1.1 [INFO] [stderr] Compiling yaml-rust v0.4.5 [INFO] [stderr] Compiling libloading v0.7.0 [INFO] [stderr] Compiling base64 v0.10.1 [INFO] [stderr] Compiling walkdir v2.3.2 [INFO] [stderr] Compiling clang-sys v1.2.0 [INFO] [stderr] Compiling aho-corasick v0.7.18 [INFO] [stderr] Compiling atty v0.2.14 [INFO] [stderr] Compiling which v3.1.1 [INFO] [stderr] Compiling time v0.1.44 [INFO] [stderr] Compiling bincode v1.3.3 [INFO] [stderr] Compiling plist v0.4.2 [INFO] [stderr] Compiling quote v1.0.9 [INFO] [stderr] Compiling flate2 v1.0.20 [INFO] [stderr] Compiling regex v1.5.4 [INFO] [stderr] Compiling cexpr v0.4.0 [INFO] [stderr] Compiling clap v2.33.3 [INFO] [stderr] Compiling chrono v0.4.19 [INFO] [stderr] Compiling ordinal v0.2.3 [INFO] [stderr] Compiling env_logger v0.8.4 [INFO] [stderr] Compiling maud_macros v0.22.2 [INFO] [stderr] Compiling maud v0.22.2 [INFO] [stderr] Compiling onig_sys v69.7.0 [INFO] [stderr] Compiling onig v5.0.0 [INFO] [stderr] Compiling syntect v3.3.0 [INFO] [stderr] Compiling blogc v0.1.0 (/opt/rustwide/workdir) [INFO] [stderr] Finished dev [unoptimized + debuginfo] target(s) in 2m 09s [INFO] running `Command { std: "docker" "inspect" "b0ff36933380a2ebf21a8f8054ba8b1303d4029a8fdd7bfe8bfe8bf7691d0973", kill_on_drop: false }` [INFO] running `Command { std: "docker" "rm" "-f" "b0ff36933380a2ebf21a8f8054ba8b1303d4029a8fdd7bfe8bfe8bf7691d0973", kill_on_drop: false }` [INFO] [stdout] b0ff36933380a2ebf21a8f8054ba8b1303d4029a8fdd7bfe8bfe8bf7691d0973 [INFO] running `Command { std: "docker" "create" "-v" "/var/lib/crater-agent-workspace/builds/worker-113/target:/opt/rustwide/target:rw,Z" "-v" "/var/lib/crater-agent-workspace/builds/worker-113/source:/opt/rustwide/workdir:ro,Z" "-v" "/var/lib/crater-agent-workspace/cargo-home:/opt/rustwide/cargo-home:ro,Z" "-v" "/var/lib/crater-agent-workspace/rustup-home:/opt/rustwide/rustup-home:ro,Z" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-e" "CARGO_INCREMENTAL=0" "-e" "RUST_BACKTRACE=full" "-e" "RUSTFLAGS=--cap-lints=forbid" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-w" "/opt/rustwide/workdir" "-m" "1610612736" "--user" "0:0" "--network" "none" "ghcr.io/rust-lang/crates-build-env/linux@sha256:d190cb871061d98bc6d0581d85cb2ecb09a0f8a142ba5463de30be9999fc3251" "/opt/rustwide/cargo-home/bin/cargo" "+61469b682c2b0bf9cebc4622f1859e2bb3b7ebca" "test" "--frozen" "--no-run" "--message-format=json", kill_on_drop: false }` [INFO] [stdout] 0e6240a5a98af56079746dd8ca9bf5f8ca72bb481d98f83766aac53c4f6ae4ca [INFO] running `Command { std: "docker" "start" "-a" "0e6240a5a98af56079746dd8ca9bf5f8ca72bb481d98f83766aac53c4f6ae4ca", kill_on_drop: false }` [INFO] [stderr] Compiling blogc v0.1.0 (/opt/rustwide/workdir) [INFO] [stderr] Finished test [unoptimized + debuginfo] target(s) in 2.99s [INFO] [stderr] Executable unittests src/lib.rs (/opt/rustwide/target/debug/deps/blog-3303ff12d02a6104) [INFO] [stderr] Executable unittests src/bin.rs (/opt/rustwide/target/debug/deps/blogc-88cf8c8a541bdc58) [INFO] running `Command { std: "docker" "inspect" "0e6240a5a98af56079746dd8ca9bf5f8ca72bb481d98f83766aac53c4f6ae4ca", kill_on_drop: false }` [INFO] running `Command { std: "docker" "rm" "-f" "0e6240a5a98af56079746dd8ca9bf5f8ca72bb481d98f83766aac53c4f6ae4ca", kill_on_drop: false }` [INFO] [stdout] 0e6240a5a98af56079746dd8ca9bf5f8ca72bb481d98f83766aac53c4f6ae4ca [INFO] running `Command { std: "docker" "create" "-v" "/var/lib/crater-agent-workspace/builds/worker-113/target:/opt/rustwide/target:rw,Z" "-v" "/var/lib/crater-agent-workspace/builds/worker-113/source:/opt/rustwide/workdir:ro,Z" "-v" "/var/lib/crater-agent-workspace/cargo-home:/opt/rustwide/cargo-home:ro,Z" "-v" "/var/lib/crater-agent-workspace/rustup-home:/opt/rustwide/rustup-home:ro,Z" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-e" "CARGO_INCREMENTAL=0" "-e" "RUST_BACKTRACE=full" "-e" "RUSTFLAGS=--cap-lints=forbid" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-w" "/opt/rustwide/workdir" "-m" "1610612736" "--user" "0:0" "--network" "none" "ghcr.io/rust-lang/crates-build-env/linux@sha256:d190cb871061d98bc6d0581d85cb2ecb09a0f8a142ba5463de30be9999fc3251" "/opt/rustwide/cargo-home/bin/cargo" "+61469b682c2b0bf9cebc4622f1859e2bb3b7ebca" "test" "--frozen", kill_on_drop: false }` [INFO] [stdout] 4f2ade584652350222e75eaf9f256ff3a7384308886b8b7fc89208defb26a3ed [INFO] running `Command { std: "docker" "start" "-a" "4f2ade584652350222e75eaf9f256ff3a7384308886b8b7fc89208defb26a3ed", kill_on_drop: false }` [INFO] [stderr] Blocking waiting for file lock on package cache [INFO] [stderr] Finished test [unoptimized + debuginfo] target(s) in 0.21s [INFO] [stderr] Running unittests src/lib.rs (/opt/rustwide/target/debug/deps/blog-3303ff12d02a6104) [INFO] [stdout] [INFO] [stdout] running 10 tests [INFO] [stdout] test post::tests::it_returns_an_error_for_an_unexpected_filename ... ok [INFO] [stdout] test post_link::tests::it_returns_an_empty_array_of_links ... ok [INFO] [stdout] test post::tests::it_renders_hard_coded_legacy_html_from_a_file ... ok [INFO] [stdout] test post::tests::it_overrides_the_path_when_url_is_specified ... ok [INFO] [stdout] test post::tests::it_parses_the_title ... ok [INFO] [stdout] test post::tests::it_calculates_an_output_path_and_directory ... ok [INFO] [stdout] test post::tests::it_parses_the_date ... ok [INFO] [stdout] test post_link::tests::it_returns_links_without_repeated_months ... ok [INFO] [stdout] test post_link::tests::it_returns_a_valid_array_of_links ... ok [INFO] [stdout] test post::tests::it_renders_html ... FAILED [INFO] [stdout] [INFO] [stdout] failures: [INFO] [stdout] [INFO] [stdout] ---- post::tests::it_renders_html stdout ---- [INFO] [stdout] thread 'post::tests::it_renders_html' panicked at 'assertion failed: `(left == right)` [INFO] [stdout] left: `"
This year I’ve decided to try to learn Rust. I’m\nfascinated by its ownership model for memory management; I’m curious what the\nclaims about safety are all about; and, I love how it incorporates ideas from\nthe functional programming world. But I haven’t gotten to all of that yet - I’m\njust getting started learning the basic syntax.
\nLearning a computer language is just like learning a human language. You have\nto try to read and write it everyday, even if just for a few minutes. You need\nto get to know some native speakers. And there’s no way around it: You need to\nlearn the basic vocabulary of the language, word by word. To make things worse,\nour human languages usually have several words that mean the same thing. Which\none should I use? Sometime only a native speaker will really know.
\nThis week I was reading about if let and match in The Rust Programming\nBook\n(TRPL). I read that if let is really syntactic sugar\nfor match:
\nThis intrigued me. The phrase “syntactic sugar” implies the two code snippets\ndon’t only produce the same results, it means the compiler generates exactly\nthe same code in each case.
\nDoes the Rust compiler really generate exactly the same code for if let as it does for match? Read\non to find out. Today I’ll start with a quick review of the syntax and meaning\nof if let and match. Then\nI’ll take a look at how Rust compiles if let and\nmatch, at what code it produces.
\nThe idea behind if let is that it compares a pattern\nwith a value:
\nIn this example if let compares the pattern Some(3) with the value some_u8_value. If there’s a match, if\nlet executes the println! code inside the\nblock.
\nif let assigns a value at the same time, when the\npattern matches the value. This is the idea behind including the let keyword\nafter if. This is more apparent if I rewrite the example using a variable i\ninstead of 3. I'll also add a main function so I can execute the code:
\n\nfn main() {\n let some_u8_value = Some(3u8);\n if let Some(i) = some_u8_value {\n println!(\"assigned {} to i\", i);\n }\n}\n\n
When I saved this in a file called if-let.rs and ran it, I got:
\n\n$ rustc if-let.rs\n$ ./main\nAssigned 3 to i\n\n
if let “unwrapped” the option structure, and assigned\nthe value 3 to the identifier i.
\nAs TRPL explains, I could also have written this using the match keyword, as follows:
\n\nfn main() {\n let some_u8_value = Some(3u8);\n match some_u8_value {\n Some(i) => println!(\"Matched: {}\", i),\n None => (),\n }\n}\n\n
To write this all I had to do was move things around a bit in my if let code snippet from above:
\nBecause there was no else clause for the if let\nstatement, I used None => () in match.
\nSaving this code in match.rs and running it I got the same result:
\n\n$ rustc match.rs\n$ ./main\nMatched: 3\n\n
I was curious though: If these two code snippets are entirely equivalent, then\nthe Rust compiler should generate exactly the same executable program when I\ncompile them. In theory, therefore, I should be able to compare the two\nexecutable binaries to test whether TRPL’s statement about syntactic sugar is\naccurate. But comparing binary executables might not work. Likely there are\ntimestamps or other ephemeral values encoded in the executable that would break\nthe comparison. I decided to look for an easier way to test the compiler’s\noutput.
\nThen I came across mid-level intermediate representation (MIR), described here\non the Rust blog. MIR is an\ninternal text language the rust compiler produces when you include the\n—emit-mir flag, like this:
\n\n$ rustc --emit mir if-let.rs\n\n
With this option specified, rust generates a file called if-let.mir. Opening up\nthis file, I see:
\n\n// WARNING: This output format is intended for human consumers only\n// and is subject to change without notice. Knock yourself out.\nfn main() -> () {\n let mut _0: (); // return pointer\n scope 1 {\n let _1: std::option::Option\n; // \"some_u8_value\" in scope 1 at src/if-let.rs:16:9: 16:22\n\netc…\n
“Knock yourself out;” now I’m really intrigued!
\nI decided to compare the MIR text file the Rust compiler produced for the if\nlet snippet vs. the match snippet. If Rust\nconsiders if let to be syntactic sugar for match, then the MIR representation of the two snippets\nshould be the same.
\nBut when I started reading the MIR code, I found the call to the println! macro generated a lot of verbose text:
\n\nlet mut _3: isize;\nlet mut _4: ();\nlet mut _5: std::fmt::Arguments;\nlet mut _6: &[&str];\nlet mut _7: &[&str; 2];\nlet mut _8: &[&str; 2];\nlet mut _9: &[std::fmt::ArgumentV1];\nlet mut _10: &[std::fmt::ArgumentV1; 1];\nlet mut _11: &[std::fmt::ArgumentV1; 1];\nlet mut _12: [std::fmt::ArgumentV1; 1];\nlet mut _13: (&u8,);\nlet mut _14: &u8;\nlet mut _16: std::fmt::ArgumentV1;\nlet mut _17: &u8;\nlet mut _18: fn(&u8, &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error>;\n\n
All of this MIR pseudocode might confuse my comparison unnecessarily, so I\ndecided to simplify my if let example by removing the\nprintln! call entirely. I rewrote the if let snippet like this (if-let.rs):
\n\nfn main() {\n let some_u8_value = Some(3u8);\n if let Some(i) = some_u8_value {\n let _ = i;\n }\n}\n\n
And the match snippet like this (match.rs):
\n\nfn main() {\n let some_u8_value = Some(3u8);\n match some_u8_value {\n Some(i) => { let _ = i; }\n None => ()\n }\n}\n\n
I also noticed the MIR file contained many comments with line numbers:
\n\n_2 = ((_1 as Some).0: u8); // scope 3 at if-let.rs:3:17: 3:18\nStorageLive(_5); // scope 3 at\n:2:27: 2:58\nStorageLive(_6); // scope 3 at :3:18: 3:43\n
I realized the line numbers would likely cause problems comparing one MIR file\nto another, so I removed all of the comments using sed:
\n\n$ rustc if-let.rs --emit mir\n$ cat if-let.mir | sed -e 's/\\/\\/.*$//' > if-let.mir.nocomments\n\n
This generates a new text file called if-let.mir.nocomments, which contains the\nsame content as if-let.mir, but with no comments. And this command processes\nthe match.rs file in the same way:
\n\n$ rustc match.rs --emit mir\n$ cat match.mir | sed -e 's/\\/\\/.*$//' > match.mir.nocomments\n\n
Now I ran a simple diff command on the simplified MIR text files. If the\ncompiler considers if let to be exactly the same as\nmatch then there should be no difference, then the\noutput of diff should be empty.
\nBut running diff I saw:
\n\n$ diff if-let.mir.nocomments match.mir.nocomments\n19c19\n< switchInt(_3) -> [1isize: bb2, otherwise: bb1];\n---\n> switchInt(_3) -> [0isize: bb1, otherwise: bb2];\n\n
My two MIR files are almost identical; the MIR text Rust generates for if let is exactly the same as the MIR text Rust generates\nfor match, except for line 19. I’ve almost proven\nthe hypothesis that if let is syntactic sugar for\nmatch, but not quite.
\nLet’s take a close look at the MIR code around line 19 and try to understand\nwhat it means. Here’s a portion of if-let.mir.nocomments, produced by the Rust\ncompiler from my if let code above:
\n\nbb0: {\n StorageLive(_1);\n _1 = std::option::Option\n::Some(const 3u8,);\n _3 = discriminant(_1);\n switchInt(_3) -> [1isize: bb2, otherwise: bb1];\n}\n\nbb1: {\n _0 = ();\n goto -> bb3;\n}\n\nbb2: {\n StorageLive(_2);\n _2 = ((_1 as Some).0: u8);\n _0 = ();\n goto -> bb3;\n}\n
I don’t understand MIR syntax, but it’s not hard to guess what’s going on. Each\nof these “bb” blocks of code { … } probably\nrepresents a logical piece of my program.
\nThe first block, bb0, seems to assign the value Some(3) to _1, and then calls\ndiscriminant(_1) and saves the “discriminant,” whatever that is, in _3.\nFinally, it tests whether the discriminant is 1. If the discriminant is 1 it\njumps to bb2, or otherwise to bb1. So bb0 likely represents the\nif portion of my if let\nsnippet, testing a condition:
\n\nif let Some(i) = some_u8_value\n\n
The bb1 block saves () in _0 and jumps to bb3. This likely represents the\nmissing/default else clause of my if let statement.
\nAnd the bb2 block saves 3, the unwrapped value inside of Some(3), in _2 and\njumps to bb3. Probably _2 is the variable i, and this block of MIR text\nrepresents the let portion of my if let snippet:
\n\nlet Some(i) = some_u8_value\nlet _ = i;\n\n
Now let’s take a look at the match version, the contents of\nmatch.mir.nocomments. It’s entirely the same, except for the switchInt line:
\n\nbb0: {\n StorageLive(_1);\n _1 = std::option::Option\n::Some(const 3u8,);\n _3 = discriminant(_1);\n switchInt(_3) -> [0isize: bb1, otherwise: bb2];\n}\n
Reading this carefully, I saw that actually it does mean the same thing: If the\ndiscriminant is 0, Rust calls the bb1 block, or\notherwise the bb2 block.
\nSo, summarizing, the if let snippet ran this\npseudo-code:
\n\nIf the discriminant is 1, call bb1, else bb2.\n\n
…and the match snippet ran this pseudo-code:
\n\nIf the discriminant is 0, call bb2, else bb1.\n\n
So, in fact, the two versions use the same logic, assuming the value of\ndiscriminant is either 0 or 1. If discriminant = 0, Rust assumes the comparison\nwas true and executes the match clause; if discriminant = 1, Rust executes the\nelse clause.
\nClearly the discriminant function is crucial - when I have time next, I’ll\nexplore what discriminant means, where it’s implemented and how it works. Or if\nanyone from the Rust teams happens to read this, let us know.
\n"`, [INFO] [stdout] right: `"This year I’ve decided to try to learn Rust. I’m\nfascinated by its ownership model for memory management; I’m curious what the\nclaims about safety are all about; and, I love how it incorporates ideas from\nthe functional programming world. But I haven’t gotten to all of that yet - I’m\njust getting started learning the basic syntax.
\nLearning a computer language is just like learning a human language. You have\nto try to read and write it everyday, even if just for a few minutes. You need\nto get to know some native speakers. And there’s no way around it: You need to\nlearn the basic vocabulary of the language, word by word. To make things worse,\nour human languages usually have several words that mean the same thing. Which\none should I use? Sometime only a native speaker will really know.
\nThis week I was reading about if let and match in The Rust Programming\nBook\n(TRPL). I read that if let is really syntactic sugar\nfor match:
\nThis intrigued me. The phrase “syntactic sugar” implies the two code snippets\ndon’t only produce the same results, it means the compiler generates exactly\nthe same code in each case.
\nDoes the Rust compiler really generate exactly the same code for if let as it does for match? Read\non to find out. Today I’ll start with a quick review of the syntax and meaning\nof if let and match. Then\nI’ll take a look at how Rust compiles if let and\nmatch, at what code it produces.
\nThe idea behind if let is that it compares a pattern\nwith a value:
\nIn this example if let compares the pattern Some(3) with the value some_u8_value. If there’s a match, if\nlet executes the println! code inside the\nblock.
\nif let assigns a value at the same time, when the\npattern matches the value. This is the idea behind including the let keyword\nafter if. This is more apparent if I rewrite the example using a variable i\ninstead of 3. I'll also add a main function so I can execute the code:
\n\nfn main() {\n let some_u8_value = Some(3u8);\n if let Some(i) = some_u8_value {\n println!(\"assigned {} to i\", i);\n }\n}\n\n
When I saved this in a file called if-let.rs and ran it, I got:
\n\n$ rustc if-let.rs\n$ ./main\nAssigned 3 to i\n\n
if let “unwrapped” the option structure, and assigned\nthe value 3 to the identifier i.
\nAs TRPL explains, I could also have written this using the match keyword, as follows:
\n\nfn main() {\n let some_u8_value = Some(3u8);\n match some_u8_value {\n Some(i) => println!(\"Matched: {}\", i),\n None => (),\n }\n}\n\n
To write this all I had to do was move things around a bit in my if let code snippet from above:
\nBecause there was no else clause for the if let\nstatement, I used None => () in match.
\nSaving this code in match.rs and running it I got the same result:
\n\n$ rustc match.rs\n$ ./main\nMatched: 3\n\n
I was curious though: If these two code snippets are entirely equivalent, then\nthe Rust compiler should generate exactly the same executable program when I\ncompile them. In theory, therefore, I should be able to compare the two\nexecutable binaries to test whether TRPL’s statement about syntactic sugar is\naccurate. But comparing binary executables might not work. Likely there are\ntimestamps or other ephemeral values encoded in the executable that would break\nthe comparison. I decided to look for an easier way to test the compiler’s\noutput.
\nThen I came across mid-level intermediate representation (MIR), described here\non the Rust blog. MIR is an\ninternal text language the rust compiler produces when you include the\n—emit-mir flag, like this:
\n\n$ rustc --emit mir if-let.rs\n\n
With this option specified, rust generates a file called if-let.mir. Opening up\nthis file, I see:
\n\n// WARNING: This output format is intended for human consumers only\n// and is subject to change without notice. Knock yourself out.\nfn main() -> () {\n let mut _0: (); // return pointer\n scope 1 {\n let _1: std::option::Option\n; // \"some_u8_value\" in scope 1 at src/if-let.rs:16:9: 16:22\n\netc…\n
“Knock yourself out;” now I’m really intrigued!
\nI decided to compare the MIR text file the Rust compiler produced for the if\nlet snippet vs. the match snippet. If Rust\nconsiders if let to be syntactic sugar for match, then the MIR representation of the two snippets\nshould be the same.
\nBut when I started reading the MIR code, I found the call to the println! macro generated a lot of verbose text:
\n\nlet mut _3: isize;\nlet mut _4: ();\nlet mut _5: std::fmt::Arguments;\nlet mut _6: &[&str];\nlet mut _7: &[&str; 2];\nlet mut _8: &[&str; 2];\nlet mut _9: &[std::fmt::ArgumentV1];\nlet mut _10: &[std::fmt::ArgumentV1; 1];\nlet mut _11: &[std::fmt::ArgumentV1; 1];\nlet mut _12: [std::fmt::ArgumentV1; 1];\nlet mut _13: (&u8,);\nlet mut _14: &u8;\nlet mut _16: std::fmt::ArgumentV1;\nlet mut _17: &u8;\nlet mut _18: fn(&u8, &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error>;\n\n
All of this MIR pseudocode might confuse my comparison unnecessarily, so I\ndecided to simplify my if let example by removing the\nprintln! call entirely. I rewrote the if let snippet like this (if-let.rs):
\n\nfn main() {\n let some_u8_value = Some(3u8);\n if let Some(i) = some_u8_value {\n let _ = i;\n }\n}\n\n
And the match snippet like this (match.rs):
\n\nfn main() {\n let some_u8_value = Some(3u8);\n match some_u8_value {\n Some(i) => { let _ = i; }\n None => ()\n }\n}\n\n
I also noticed the MIR file contained many comments with line numbers:
\n\n_2 = ((_1 as Some).0: u8); // scope 3 at if-let.rs:3:17: 3:18\nStorageLive(_5); // scope 3 at\n:2:27: 2:58\nStorageLive(_6); // scope 3 at :3:18: 3:43\n
I realized the line numbers would likely cause problems comparing one MIR file\nto another, so I removed all of the comments using sed:
\n\n$ rustc if-let.rs --emit mir\n$ cat if-let.mir | sed -e 's/\\/\\/.*$//' > if-let.mir.nocomments\n\n
This generates a new text file called if-let.mir.nocomments, which contains the\nsame content as if-let.mir, but with no comments. And this command processes\nthe match.rs file in the same way:
\n\n$ rustc match.rs --emit mir\n$ cat match.mir | sed -e 's/\\/\\/.*$//' > match.mir.nocomments\n\n
Now I ran a simple diff command on the simplified MIR text files. If the\ncompiler considers if let to be exactly the same as\nmatch then there should be no difference, then the\noutput of diff should be empty.
\nBut running diff I saw:
\n\n$ diff if-let.mir.nocomments match.mir.nocomments\n19c19\n< switchInt(_3) -> [1isize: bb2, otherwise: bb1];\n---\n> switchInt(_3) -> [0isize: bb1, otherwise: bb2];\n\n
My two MIR files are almost identical; the MIR text Rust generates for if let is exactly the same as the MIR text Rust generates\nfor match, except for line 19. I’ve almost proven\nthe hypothesis that if let is syntactic sugar for\nmatch, but not quite.
\nLet’s take a close look at the MIR code around line 19 and try to understand\nwhat it means. Here’s a portion of if-let.mir.nocomments, produced by the Rust\ncompiler from my if let code above:
\n\nbb0: {\n StorageLive(_1);\n _1 = std::option::Option\n::Some(const 3u8,);\n _3 = discriminant(_1);\n switchInt(_3) -> [1isize: bb2, otherwise: bb1];\n}\n\nbb1: {\n _0 = ();\n goto -> bb3;\n}\n\nbb2: {\n StorageLive(_2);\n _2 = ((_1 as Some).0: u8);\n _0 = ();\n goto -> bb3;\n}\n
I don’t understand MIR syntax, but it’s not hard to guess what’s going on. Each\nof these “bb” blocks of code { … } probably\nrepresents a logical piece of my program.
\nThe first block, bb0, seems to assign the value Some(3) to _1, and then calls\ndiscriminant(_1) and saves the “discriminant,” whatever that is, in _3.\nFinally, it tests whether the discriminant is 1. If the discriminant is 1 it\njumps to bb2, or otherwise to bb1. So bb0 likely represents the\nif portion of my if let\nsnippet, testing a condition:
\n\nif let Some(i) = some_u8_value\n\n
The bb1 block saves () in _0 and jumps to bb3. This likely represents the\nmissing/default else clause of my if let statement.
\nAnd the bb2 block saves 3, the unwrapped value inside of Some(3), in _2 and\njumps to bb3. Probably _2 is the variable i, and this block of MIR text\nrepresents the let portion of my if let snippet:
\n\nlet Some(i) = some_u8_value\nlet _ = i;\n\n
Now let’s take a look at the match version, the contents of\nmatch.mir.nocomments. It’s entirely the same, except for the switchInt line:
\n\nbb0: {\n StorageLive(_1);\n _1 = std::option::Option\n::Some(const 3u8,);\n _3 = discriminant(_1);\n switchInt(_3) -> [0isize: bb1, otherwise: bb2];\n}\n
Reading this carefully, I saw that actually it does mean the same thing: If the\ndiscriminant is 0, Rust calls the bb1 block, or\notherwise the bb2 block.
\nSo, summarizing, the if let snippet ran this\npseudo-code:
\n\nIf the discriminant is 1, call bb1, else bb2.\n\n
…and the match snippet ran this pseudo-code:
\n\nIf the discriminant is 0, call bb2, else bb1.\n\n
So, in fact, the two versions use the same logic, assuming the value of\ndiscriminant is either 0 or 1. If discriminant = 0, Rust assumes the comparison\nwas true and executes the match clause; if discriminant = 1, Rust executes the\nelse clause.
\nClearly the discriminant function is crucial - when I have time next, I’ll\nexplore what discriminant means, where it’s implemented and how it works. Or if\nanyone from the Rust teams happens to read this, let us know.
\n"`', src/post.rs:265:9 [INFO] [stdout] stack backtrace: [INFO] [stdout] 0: 0x55fa23b9822d - std::backtrace_rs::backtrace::libunwind::trace::h44a778e1067a5f21 [INFO] [stdout] at /rustc/61469b682c2b0bf9cebc4622f1859e2bb3b7ebca/library/std/src/../../backtrace/src/backtrace/libunwind.rs:93:5 [INFO] [stdout] 1: 0x55fa23b9822d - std::backtrace_rs::backtrace::trace_unsynchronized::h16574a507425748e [INFO] [stdout] at /rustc/61469b682c2b0bf9cebc4622f1859e2bb3b7ebca/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5 [INFO] [stdout] 2: 0x55fa23b9822d - std::sys_common::backtrace::_print_fmt::h861b0076a4de945a [INFO] [stdout] at /rustc/61469b682c2b0bf9cebc4622f1859e2bb3b7ebca/library/std/src/sys_common/backtrace.rs:66:5 [INFO] [stdout] 3: 0x55fa23b9822d -