2 title: "Hello World, in 0x A Presses"
4 summary: "Write a Hello World program in Rust, without ever pressing the A key, and learn some things along the way."
7 ### The (Mario) A Button Challenge
9 In the Super Mario 64 speedrunning scene, many people have gotten good enough at the game that doing normal speedrunning isn't challenging enough anymore.
10 In order to add tha difficulty back in, there are many "Category Extensions" that add some extra challenge on top.
11 One of the most interesting of these is the A Button Challenge, where runners attempt to play the game pressing the A button as few times as possible.
12 Since the A button is the jump button, and this is a game about jumping, this leads to quite a few creative solutions and alternate routes.
13 "Watch For Rolling Rocks - 0.5x A Presses" is an excellent video showing one of those routes.
14 If you haven't watched it before, it's much more interesting than anything I can write, watch it first :).
16 {% youtube video="kpk2tdsPh0A" %}
18 ### The (Programming) A Button Challenge
20 In mid-October I tweeted a joke about doing the "Advent of Code" A Button Challenge.
22 {% tweet name="genderAlgebraist", at="porglezomp", date="2019-10-16",
23 link="https://twitter.com/porglezomp/status/1184561096859869184" %}
24 Advent of Code, A Button Challenge: solve all programs without ever writing the letter A in any of them.
27 This inspired some discussion in the replies by Swift compiler folks discussing ways to try to handle programs without needing `var`.
30 Just a few days later I was at [Rust Belt Rust][rbr].
31 After lunch, I was hanging out in the hall with some people, and we were sharing jokes, and I made the Rust A Button Challenge joke.
32 At first it doesn't sound too bad, until you realize that `main` has an `a` in it, and so you're immediately in trouble.
33 We started thinking through different approaches, and eventually got a working solution, and eventually a polished solution.
34 Doing this looks at a wide variety of interesting topics in Rust, and so it's worth sharing.
36 [rbr]: https://rust-belt-rust.com/
38 ### Hello, Rust! (Without the Prohibited Letter!)
40 So, we want to write Hello World.
47 And we've already hit trouble.
48 We can't even write `cargo`.
49 Luckily, bash has our backs here, and we can use command substitution with `printf` to get it.
52 $ $(printf 'c\141rgo init button')
55 Because this is such a mechanical replacement, for legibility I'm gonna just write the normal commands even when they would include an `a`, and you can assume that we're using the `printf` method.
56 Technically, this already just wrote a Hello World for us, in `main.rs`, due to the default project.
57 Let's say, though, that this doesn't meet the spirit of the challenge, and we have to rewrite it.
58 We can't write it the usual way, with:
62 println!("Hello, World!");
66 Because `main` has an `a` right there!
67 So, we'll have to get more creative.
68 There are a few ways to override the entry point if you don't want to use Rust's default `main`.
76 And even if it did, it needs a `#![feature]` to enable it, which would cause trouble on its own.
78 Libraries have ways to have global symbols that are called at the start.
79 There are various mechanisms on different platforms, but they're all troublesome.
80 If we wanted to name a function `__init` (which is one of the Linux symbols for the purpose) and have it be recognized, then we'd need either `#[link_name]` or `#[no_mangle]`, both of which needs an `a`.
81 There are ways to get the correct setup using a `pub static`... but that also uses an `a`.
82 So, in the end we've exhausted all the options here.
84 What we really want is some attribute which adds a new, custom entry point, we want it to be in the standard library, since otherwise we could cheat by using some external macro written with the letter `a` that does all the work for us.
85 We can't write one ourselves *without* using the letter `a`, because we'd have to write `proc_macro`.
86 But, it turns out, we don't need to, since one of these has been sitting in front of us all along.
92 Let's write our first working canidate for Hello World!
97 println!("Hello, World!");
101 And now we can run it:
108 test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
114 test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
117 Ah right, we need `--nocapture` if we want to see our output.
120 $ cargo test -- --nocapture
124 test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
130 test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
133 So we've got Hello World for sure, but we also have a bunch of other junk too.
134 We can clean that up slightly with a few more flags, `--lib` will get rid of the doctest, and `--quiet` will remove slightly more.
137 $ cargo test --lib --quiet -- --nocapture
142 test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
145 And that's about as clean as we can get with command line arguments.
146 This still isn't great.
147 But we can do better.
148 First, let's avoid that extra text afterward.
149 Rust's tests are run in-process, so if we just exit immediately, then we clean things up a bit more.
150 Now, with this program:
155 println!("Hello, World!");
156 std::process::exit(0);
160 We get a much cleaner output:
168 We can deal with that initial text as well, too.
169 You can expect that essentially all terminals implement [ANSI escape sequences][ansi], which allow you to control the cursor, mess with text, and more.
170 In this case, we want to use the sequence `<Esc>[<Value>A`, which is "Cursor Up."
172 [ansi]: http://ascii-table.com/ansi-escape-sequences.php
174 > Moves the cursor up by the specified number of lines without changing columns. If the cursor is already on the top line, ANSI.SYS ignores this sequence.
176 Using this, we can go back to the previous lines and overwrite them with spaces, and then go back yet again to write on the first line.
181 print!("\x1b[1\x41 \x1b[1\x41\r");
182 println!("Hello, World!");
183 std::process::exit(0);
187 This works in 4 steps.
188 First, `\x1b[1\x41` moves up by one line.
189 Then, the sequence of spaces overwrites the existing text on the line.
190 We repeat the `\x1b[1\x41` sequence to move up on the the first line, and then finally use `\r` to go back to the beginning of the line.
191 Now text can write from there and it will all be hidden.
192 Finally, this prints only:
202 We've written our program without using the A key, but maybe you're not satisfied.
203 We've got a big 'ol `Cargo.toml` with an `a`, and plenty of `cargo` on the command line.
204 Sure we never have to type any of that, but maybe you think that's against the spirit of the thing?
205 After all, I've been using bash here, but using a substitution as your command flat-out doesn't work in Fish, my shell of choice.
206 If you want to do that, you need `eval`... and you can see where this breaks down.
208 Luckily, we don't *have* to use Cargo.
209 We can use `rustc`, a compiler which graciously has no `a` in its name.
210 Let's start with the program we finished with in the last section.
213 $ vim hello.rs # Type it in without using the forbidden letter!
214 $ rustc --test hello.rs
221 This time we can't write `--nocapture`, so we don't get any output, and we don't overwrite the harness text.
222 The test harness is capturing out stdout, so we need to get it back.
223 This calls for one final trick: let's reopen `stdout` again.
230 let mut f = std::fs::File::open("/dev/stdout").expect("stdout");
231 writeln!(f, "\x1b[1\x41 \x1b[2\x41\r")
233 writeln!(f, "Hello, World!").expect("write");
234 std::process::exit(0);
238 Running this, we get:
241 $ rustc --test hello.rs
246 Truly in 0x A presses.
248 Thanks to [Pannenkoek2012][] for popularizing the Super Mario 64 A Button Challenge, and making the amazing video.
249 Thanks to [@quietmisdreavus][] for giving suggestions for flags to use when testing with Rust, and [@myrrlyn][] for suggesting ANSI escapes.
250 You can take a look at [many revisions of a gist containing the program][gist].
252 If you try an A Button Challenge in your programming language of choice, [send me a tweet][] telling me about it and what was difficult!
254 [Pannenkoek2012]: https://www.youtube.com/user/pannenkoek2012
255 [@quietmisdreavus]: https://twitter.com/quietmisdreavus
256 [@myrrlyn]: https://twitter.com/myrrlyn
257 [send me a tweet]: https://twitter.com/porglezomp
258 [gist]: https://gist.github.com/porglezomp/68f4d9e7be29b758284a7b897269c718/revisions