DEV Community

Tim McNamara
Tim McNamara

Posted on • Edited on

Deadbeef? Just say no. Let's learn to build a small Rust app to find out what words can you spell with the letters A-F

I'm always really irritated when I see "babe" and "deadbeef" (the worst) in code examples that use hexadecimal numbers. It's actually not that funny.

You know you can do the dab in hex, right?

Doing the Dab

Photo: User:Gokudabbing / Wikipedia

What else is there? Here's a selection (the full list is at the bottom of the post).

Nouns

  • ace (perhaps that should be an adjective, it's time for a 90s linguistic revival)
  • bead
  • bee
  • cab
  • dad
  • fad

Adjectives

  • decaf
  • deaf
  • dead
  • deed
  • faded

Verbs

  • add
  • cede
  • fade
  • dab
  • feed

How do we find them?

Here's the full source code. If you're new to Rust, keep reading&mdashlI explain what's going on below.

use std::fs::File;
use std::io::{BufReader, BufRead};

fn main() -> std::io::Result<()> {
    let f = File::open("/usr/share/dict/words")?;
    let reader = BufReader::new(f);

    'lines: for line in reader.lines() {
        let word = line.unwrap();
        for byte in word.bytes() {
            match byte {
                b'A' | b'B' | b'C' | b'D' | b'E' | b'F' |
                b'a' | b'b' | b'c' | b'd' | b'e' | b'f' => continue,
                _ => continue 'lines,
            }
        }

        if word.len() > 2 {
            println!("{}", word);
        }
    };

    Ok(())
}

Enter fullscreen mode Exit fullscreen mode

Let's break the example down. We start with imports.

use std::fs::File;
use std::io::{BufReader, BufRead};
Enter fullscreen mode Exit fullscreen mode

We're bringing some of the standard library into local scope. That gives us the ability to open files. File can open them. A BufReader can read them efficiently. We also need to import BufReader's "traits" along with it to avoid name clashes. Traits define methods, and we can sort of pick and mix the methods that we want for any given type.

Hint: If you haven't encountered the term trait before, think of it as an Abstract Base Class or an interface. If you're new to programming and haven't heard of those two either, that's ok. It's not essential to know what a trait is for the rest of the post.

fn main() -> std::io::Result<()> {
    // ...
    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

The std::io::Result<()> type is a bit crazy, sorry about that. It's an indicator that something might fail under the hood—like the hard drive might break or we could trigger a permissions error—and Rust should be prepared for that.

Adding Ok(()) at the end is our main() function saying "everything went fine". We are returning Ok with a () type, which has the fun name "unit". It is a placeholder value.

let f = File::open("/usr/share/dict/words")?;
Enter fullscreen mode Exit fullscreen mode

I run Ubuntu, which includes a list of valid-ish words as a plain text file. We open that up. If we do end up triggering a permissions error, or the file is deleted, the ? at the end will propagate the error and immediately exit the main() function.

let reader = BufReader::new(f);
Enter fullscreen mode Exit fullscreen mode

We create a BufReader that knows how to read from File objects efficiently. It's called BufReader because it has contains an internal in-memory buffer that can and then ask it to inject anything it reads into the string we create next.

Now comes the best bits, or why I love Rust:

    'lines: for line in reader.lines() {
        let word = line.unwrap();
        for byte in word.bytes() {
            match byte {
                b'A' | b'B' | b'C' | b'D' | b'E' | b'F' |
                b'a' | b'b' | b'c' | b'd' | b'e' | b'f' => continue,
                _ => continue 'lines,
            }
        }

        if word.len() > 2 {
            println!("{}", word);
        }
    };
Enter fullscreen mode Exit fullscreen mode

This chunk of code is a bit of a showoff. We define a named loop, 'lines that can be used later to immediately abort from a word that contains a character outside of our limited alphabet. And then it uses the match syntax to elegantly match bytes that we care about. (The b prefix on all of those literals indicates to Rust that they should be treated as 8-bit integers, not as characters or strings - for the details check a Rust book)

Compiling the code

Let's see the result!

First let's build a new project:

$ cargo new hexwords
     Created binary (application) `hexwords` package
Enter fullscreen mode Exit fullscreen mode
$ tree hexwords
hexwords
├── Cargo.toml
└── src
    └── main.rs
Enter fullscreen mode Exit fullscreen mode

Now copy the source code into the main.rs file. Once that's done, we can run it.

$ cd hexwords

$ cargo run -q
Abe
Ada
BBB
Bede
Beebe
Dacca
Dada
Dec
Decca
Dee
Edda
Feb
abed
accede
acceded
ace
aced
add
added
baa
baaed
babe
bad
bade
bead
beaded
bed
bedded
bee
beef
beefed
cab
cabbed
cad
cede
ceded
dab
dabbed
dad
dead
deaf
deb
decade
decaf
deed
deeded
deface
defaced
ebb
ebbed
efface
effaced
facade
face
faced
fad
fade
faded
fed
fee
feed
Enter fullscreen mode Exit fullscreen mode

Voilà!

Full source code here:

https://github.com/timClicks/hexwords

Top comments (1)

Collapse
 
timclicks profile image
Tim McNamara

This post has spurred @dorsmay into trying if he can make something faster #codegolf ftw