]> Witch of Git - web/blog/blob - posts/2019/hello-world-in-0x-a-presses.md
Remove console.log
[web/blog] / posts / 2019 / hello-world-in-0x-a-presses.md
1 ---
2 title: "Hello World, in 0x A Presses"
3 date: 2019-11-02
4 summary: "Write a Hello World program in Rust, without ever pressing the A key, and learn some things along the way."
5 ---
6
7 ### The (Mario) A Button Challenge
8
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 the 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 :).
15
16 {% youtube video="kpk2tdsPh0A" %}
17
18 ### The (Programming) A Button Challenge
19
20 In mid-October I tweeted a joke about doing the "Advent of Code" A Button Challenge.
21
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.
25 {% endtweet %}
26
27 This inspired some discussion in the replies by Swift compiler folks discussing ways to try to handle programs without needing `var`.
28 It was a lot of fun!
29
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.
35
36 [rbr]: https://rust-belt-rust.com/
37
38 ### Hello, Rust! (Without the Prohibited Letter!)
39
40 So, we want to write Hello World.
41 Let's make a project.
42
43 ```bash
44 $ cargo init button
45 ```
46
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.
50
51 ```bash
52 $ $(printf 'c\141rgo init button')
53 ```
54
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:
59
60 ```rust
61 fn main() {
62 println!("Hello, World!");
63 }
64 ```
65
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`.
69 We could try...
70
71 ```
72 #[start]
73 ```
74
75 Well that won't work.
76 And even if it did, it needs a `#![feature]` to enable it, which would cause trouble on its own.
77
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.
83
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.
87
88 ```rust
89 #[test]
90 ```
91
92 Let's write our first working candidate for Hello World!
93
94 ```rust
95 #[test]
96 fn hello() {
97 println!("Hello, World!");
98 }
99 ```
100
101 And now we can run it:
102
103 ```bash
104 $ cargo test
105 running 1 test
106 test test ... ok
107
108 test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
109
110 Doc-tests abc
111
112 running 0 tests
113
114 test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
115 ```
116
117 Ah right, we need `--nocapture` if we want to see our output.
118
119 ```bash
120 $ cargo test -- --nocapture
121 Hello, World!
122 test test ... ok
123
124 test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
125
126 Doc-tests abc
127
128 running 0 tests
129
130 test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
131 ```
132
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.
135
136 ```bash
137 $ cargo test --lib --quiet -- --nocapture
138
139 running 1 test
140 Hello, World!
141 .
142 test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
143 ```
144
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:
151
152 ```rust
153 #[test]
154 fn test() {
155 println!("Hello, World!");
156 std::process::exit(0);
157 }
158 ```
159
160 We get a much cleaner output:
161
162 ```
163
164 running 1 test
165 Hello, World!
166 ```
167
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."
171
172 [ansi]: http://ascii-table.com/ansi-escape-sequences.php
173
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.
175
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.
177
178 ```rust
179 #[test]
180 fn test() {
181 print!("\x1b[1\x41 \x1b[1\x41\r");
182 println!("Hello, World!");
183 std::process::exit(0);
184 }
185 ```
186
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:
193
194 ```bash
195 $ cargo test
196 Hello, World!
197 ```
198
199 ### Not Done Yet?
200
201 Ok.
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.
207
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.
211
212 ```bash
213 $ vim hello.rs # Type it in without using the forbidden letter!
214 $ rustc --test hello.rs
215 $ ./hello
216
217 running 1 test
218 ```
219
220 Hm
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.
224
225 ```rust
226 use std::io::Write;
227
228 #[test]
229 fn test() {
230 let mut f = std::fs::File::open("/dev/stdout").expect("stdout");
231 writeln!(f, "\x1b[1\x41 \x1b[2\x41\r")
232 .expect("write");
233 writeln!(f, "Hello, World!").expect("write");
234 std::process::exit(0);
235 }
236 ```
237
238 Running this, we get:
239
240 ```bash
241 $ rustc --test hello.rs
242 $ ./hello --quiet
243 Hello, World!
244 ```
245
246 Truly in 0x A presses.
247
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].
251
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!
253
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