]> Witch of Git - web/blog/blob - posts/2019/hello-world-in-0x-a-presses.md
Does this make twitter cards work again
[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 tags:
5 - rust
6 summary: "Write a Hello World program in Rust, without ever pressing the A key, and learn some things along the way."
7 ---
8
9 ### The (Mario) A Button Challenge
10
11 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.
12 In order to add the difficulty back in, there are many "Category Extensions" that add some extra challenge on top.
13 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.
14 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.
15 "Watch For Rolling Rocks - 0.5x A Presses" is an excellent video showing one of those routes.
16 If you haven't watched it before, it's much more interesting than anything I can write, watch it first :).
17
18 {% youtube video="kpk2tdsPh0A" %}
19
20 ### The (Programming) A Button Challenge
21
22 In mid-October I tweeted a joke about doing the "Advent of Code" A Button Challenge.
23
24 {% tweet name="genderAlgebraist", at="porglezomp", date="2019-10-16",
25 link="https://twitter.com/porglezomp/status/1184561096859869184" %}
26 Advent of Code, A Button Challenge: solve all programs without ever writing the letter A in any of them.
27 {% endtweet %}
28
29 This inspired some discussion in the replies by Swift compiler folks discussing ways to try to handle programs without needing `var`.
30 It was a lot of fun!
31
32 Just a few days later I was at [Rust Belt Rust][rbr].
33 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.
34 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.
35 We started thinking through different approaches, and eventually got a working solution, and eventually a polished solution.
36 Doing this looks at a wide variety of interesting topics in Rust, and so it's worth sharing.
37
38 [rbr]: https://rust-belt-rust.com/
39
40 ### Hello, Rust! (Without the Prohibited Letter!)
41
42 So, we want to write Hello World.
43 Let's make a project.
44
45 ```bash
46 $ cargo init button
47 ```
48
49 And we've already hit trouble.
50 We can't even write `cargo`.
51 Luckily, bash has our backs here, and we can use command substitution with `printf` to get it.
52
53 ```bash
54 $ $(printf 'c\141rgo init button')
55 ```
56
57 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.
58 Technically, this already just wrote a Hello World for us, in `main.rs`, due to the default project.
59 Let's say, though, that this doesn't meet the spirit of the challenge, and we have to rewrite it.
60 We can't write it the usual way, with:
61
62 ```rust
63 fn main() {
64 println!("Hello, World!");
65 }
66 ```
67
68 Because `main` has an `a` right there!
69 So, we'll have to get more creative.
70 There are a few ways to override the entry point if you don't want to use Rust's default `main`.
71 We could try...
72
73 ```
74 #[start]
75 ```
76
77 Well that won't work.
78 And even if it did, it needs a `#![feature]` to enable it, which would cause trouble on its own.
79
80 Libraries have ways to have global symbols that are called at the start.
81 There are various mechanisms on different platforms, but they're all troublesome.
82 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`.
83 There are ways to get the correct setup using a `pub static`... but that also uses an `a`.
84 So, in the end we've exhausted all the options here.
85
86 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.
87 We can't write one ourselves *without* using the letter `a`, because we'd have to write `proc_macro`.
88 But, it turns out, we don't need to, since one of these has been sitting in front of us all along.
89
90 ```rust
91 #[test]
92 ```
93
94 Let's write our first working candidate for Hello World!
95
96 ```rust
97 #[test]
98 fn hello() {
99 println!("Hello, World!");
100 }
101 ```
102
103 And now we can run it:
104
105 ```bash
106 $ cargo test
107 running 1 test
108 test test ... ok
109
110 test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
111
112 Doc-tests abc
113
114 running 0 tests
115
116 test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
117 ```
118
119 Ah right, we need `--nocapture` if we want to see our output.
120
121 ```bash
122 $ cargo test -- --nocapture
123 Hello, World!
124 test test ... ok
125
126 test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
127
128 Doc-tests abc
129
130 running 0 tests
131
132 test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
133 ```
134
135 So we've got Hello World for sure, but we also have a bunch of other junk too.
136 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
138 ```bash
139 $ cargo test --lib --quiet -- --nocapture
140
141 running 1 test
142 Hello, World!
143 .
144 test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
145 ```
146
147 And that's about as clean as we can get with command line arguments.
148 This still isn't great.
149 But we can do better.
150 First, let's avoid that extra text afterward.
151 Rust's tests are run in-process, so if we just exit immediately, then we clean things up a bit more.
152 Now, with this program:
153
154 ```rust
155 #[test]
156 fn test() {
157 println!("Hello, World!");
158 std::process::exit(0);
159 }
160 ```
161
162 We get a much cleaner output:
163
164 ```
165
166 running 1 test
167 Hello, World!
168 ```
169
170 We can deal with that initial text as well, too.
171 You can expect that essentially all terminals implement [ANSI escape sequences][ansi], which allow you to control the cursor, mess with text, and more.
172 In this case, we want to use the sequence `<Esc>[<Value>A`, which is "Cursor Up."
173
174 [ansi]: http://ascii-table.com/ansi-escape-sequences.php
175
176 > 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.
177
178 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.
179
180 ```rust
181 #[test]
182 fn test() {
183 print!("\x1b[1\x41 \x1b[1\x41\r");
184 println!("Hello, World!");
185 std::process::exit(0);
186 }
187 ```
188
189 This works in 4 steps.
190 First, `\x1b[1\x41` moves up by one line.
191 Then, the sequence of spaces overwrites the existing text on the line.
192 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.
193 Now text can write from there and it will all be hidden.
194 Finally, this prints only:
195
196 ```bash
197 $ cargo test
198 Hello, World!
199 ```
200
201 ### Not Done Yet?
202
203 Ok.
204 We've written our program without using the A key, but maybe you're not satisfied.
205 We've got a big 'ol `Cargo.toml` with an `a`, and plenty of `cargo` on the command line.
206 Sure we never have to type any of that, but maybe you think that's against the spirit of the thing?
207 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.
208 If you want to do that, you need `eval`... and you can see where this breaks down.
209
210 Luckily, we don't *have* to use Cargo.
211 We can use `rustc`, a compiler which graciously has no `a` in its name.
212 Let's start with the program we finished with in the last section.
213
214 ```bash
215 $ vim hello.rs # Type it in without using the forbidden letter!
216 $ rustc --test hello.rs
217 $ ./hello
218
219 running 1 test
220 ```
221
222 Hm
223 This time we can't write `--nocapture`, so we don't get any output, and we don't overwrite the harness text.
224 The test harness is capturing out stdout, so we need to get it back.
225 This calls for one final trick: let's reopen `stdout` again.
226
227 ```rust
228 use std::io::Write;
229
230 #[test]
231 fn test() {
232 let mut f = std::fs::File::open("/dev/stdout").expect("stdout");
233 writeln!(f, "\x1b[1\x41 \x1b[2\x41\r")
234 .expect("write");
235 writeln!(f, "Hello, World!").expect("write");
236 std::process::exit(0);
237 }
238 ```
239
240 Running this, we get:
241
242 ```bash
243 $ rustc --test hello.rs
244 $ ./hello --quiet
245 Hello, World!
246 ```
247
248 Truly in 0x A presses.
249
250 Thanks to [Pannenkoek2012][] for popularizing the Super Mario 64 A Button Challenge, and making the amazing video.
251 Thanks to [@quietmisdreavus][] for giving suggestions for flags to use when testing with Rust, and [@myrrlyn][] for suggesting ANSI escapes.
252 You can take a look at [many revisions of a gist containing the program][gist].
253
254 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!
255
256 [Pannenkoek2012]: https://www.youtube.com/user/pannenkoek2012
257 [@quietmisdreavus]: https://twitter.com/quietmisdreavus
258 [@myrrlyn]: https://twitter.com/myrrlyn
259 [send me a tweet]: https://twitter.com/porglezomp
260 [gist]: https://gist.github.com/porglezomp/68f4d9e7be29b758284a7b897269c718/revisions