Witch of Light

Webrings    Archive    RSS
A collection of Cassie's harebrained schemes.

Hello World, in 0x A Presses

Published 2019-11-02

Frantic Video

If you want this content compressed into a 4 minute video of the lightning talk, then it’s available now! It’s considerably more frantic than the blog post. Also note that it has some audible breathing in the first half, if that’s the sort of thing that bothers you.

The (Mario) A Button Challenge

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. In order to add the difficulty back in, there are many “Category Extensions” that add some extra challenge on top. 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. 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. “Watch For Rolling Rocks - 0.5x A Presses” is an excellent video showing one of those routes. If you haven’t watched it before, it’s much more interesting than anything I can write, watch it first :).

The (Programming) A Button Challenge

In mid-October I tweeted a joke about doing the “Advent of Code” A Button Challenge.

This inspired some discussion in the replies by Swift compiler folks discussing ways to try to handle programs without needing var. It was a lot of fun!

Just a few days later I was at Rust Belt Rust. 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. 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. We started thinking through different approaches, and eventually got a working solution, and eventually a polished solution. Doing this looks at a wide variety of interesting topics in Rust, and so it’s worth sharing.

Hello, Rust! (Without the Prohibited Letter!)

So, we want to write Hello World. Let’s make a project.

$ cargo init button

And we’ve already hit trouble. We can’t even write cargo. Luckily, bash has our backs here, and we can use command substitution with printf to get it.

$ $(printf 'c\141rgo init button')

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. Technically, this already just wrote a Hello World for us, in main.rs, due to the default project. Let’s say, though, that this doesn’t meet the spirit of the challenge, and we have to rewrite it. We can’t write it the usual way, with:

fn main() {
println!("Hello, World!");
}

Because main has an a right there! So, we’ll have to get more creative. There are a few ways to override the entry point if you don’t want to use Rust’s default main. We could try…

#[start]

Well that won’t work. And even if it did, it needs a #![feature] to enable it, which would cause trouble on its own.

Libraries have ways to have global symbols that are called at the start. There are various mechanisms on different platforms, but they’re all troublesome. 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. There are ways to get the correct setup using a pub static… but that also uses an a. So, in the end we’ve exhausted all the options here.

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. We can’t write one ourselves without using the letter a, because we’d have to write proc_macro. But, it turns out, we don’t need to, since one of these has been sitting in front of us all along.

#[test]

Let’s write our first working candidate for Hello World!

#[test]
fn hello() {
println!("Hello, World!");
}

And now we can run it:

$ cargo test
running 1 test
test test ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Doc-tests abc

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Ah right, we need --nocapture if we want to see our output.

$ cargo test -- --nocapture
Hello, World!
test test ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Doc-tests abc

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

So we’ve got Hello World for sure, but we also have a bunch of other junk too. We can clean that up slightly with a few more flags, --lib will get rid of the doctest, and --quiet will remove slightly more.

$ cargo test --lib --quiet -- --nocapture

running 1 test
Hello, World!
.
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

And that’s about as clean as we can get with command line arguments. This still isn’t great. But we can do better. First, let’s avoid that extra text afterward. Rust’s tests are run in-process, so if we just exit immediately, then we clean things up a bit more. Now, with this program:

#[test]
fn test() {
println!("Hello, World!");
std::process::exit(0);
}

We get a much cleaner output:


running 1 test
Hello, World!

We can deal with that initial text as well, too. You can expect that essentially all terminals implement ANSI escape sequences, which allow you to control the cursor, mess with text, and more. In this case, we want to use the sequence <Esc>[<Value>A, which is “Cursor Up.”

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.

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.

#[test]
fn test() {
print!("\x1b[1\x41 \x1b[1\x41\r");
println!("Hello, World!");
std::process::exit(0);
}

This works in 4 steps. First, \x1b[1\x41 moves up by one line. Then, the sequence of spaces overwrites the existing text on the line. 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. Now text can write from there and it will all be hidden. Finally, this prints only:

$ cargo test
Hello, World!

Not Done Yet?

Ok. We’ve written our program without using the A key, but maybe you’re not satisfied. We’ve got a big 'ol Cargo.toml with an a, and plenty of cargo on the command line. Sure we never have to type any of that, but maybe you think that’s against the spirit of the thing? 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. If you want to do that, you need eval… and you can see where this breaks down.

Luckily, we don’t have to use Cargo. We can use rustc, a compiler which graciously has no a in its name. Let’s start with the program we finished with in the last section.

$ vim hello.rs  # Type it in without using the forbidden letter!
$ rustc --test hello.rs
$ ./hello

running 1 test

Hm This time we can’t write --nocapture, so we don’t get any output, and we don’t overwrite the harness text. The test harness is capturing out stdout, so we need to get it back. This calls for one final trick: let’s reopen stdout again.

use std::io::Write;

#[test]
fn test() {
let mut f = std::fs::File::open("/dev/stdout").expect("stdout");
writeln!(f, "\x1b[1\x41 \x1b[2\x41\r")
.expect("write");
writeln!(f, "Hello, World!").expect("write");
std::process::exit(0);
}

Running this, we get:

$ rustc --test hello.rs
$ ./hello --quiet
Hello, World!

Truly in 0x A presses.

Thanks to Pannenkoek2012 for popularizing the Super Mario 64 A Button Challenge, and making the amazing video. Thanks to @quietmisdreavus for giving suggestions for flags to use when testing with Rust, and @myrrlyn for suggesting ANSI escapes. You can take a look at many revisions of a gist containing the program.

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!