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