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