So after a very long and painful phone call to ASUS ™️ about a dead motherboard, I decided that knowing the NATO phonetic alphabet would have cut that phone call in half.

And since I’ve been meaning to learn rust for a while now, I decided why not learn both?

It took a while to figure out how to use a global static variable, because rust really doesn’t like that.

TL;DR:

./src/alphabet.rs (source)

// you may ask why I used `&'static str` and not just `String`,
// and the answer is that it was mostly because I could?
static ALPHABET: OnceLock<HashMap<char, &'static str>> = OnceLock::new();

fn init_alphabet() -> HashMap<char, &'static str> { ... }

pub fn get_alphabet()  -> HashMap<char, &'static str> {
	ALPHABET.get_or_init(init_alphabet).clone()
}

pub fn to_alphabet(s: String) -> Vec<(char, String)> {
	let mut ret = Vec::<(char, String)>::new();
	let alphabet = alphabet();
	for c in s.to_lowercase().chars() {
		let cstr = c.to_string();
		let w = alphabet.get(&c).unwrap_or(&cstr.as_str()).to_string();
		ret.push((c, w.to_string()));
	}
	ret
}

Once that was done, I couldn’t decide how to make the interface, so I went with a simple cli:

./src/main.rs (cli only)

mod alphabet;
fn main() {
	// skip first arg, which is the program's name
	for arg in std::env::args().skip(1) {
		println!("-> {} <-", arg);
		for (c, s) in alphabet::to_alphabet(arg) {
			println!("{} -> {}", c, s);
		}
	}
}

And that worked fine, it’s simple and does that job.

cargo run -- hello world
-> hello <-
h -> hotel
e -> echo
l -> lima
l -> lima
o -> oscar
-> world <-
w -> whiskey
o -> oscar
r -> romeo
l -> lima
d -> delta

But I decided it needed to have a web interface, and since I’ve also been meaning to learn WASM, and I couldn’t sleep at the time, so I decided to write the interface in UI in rust/wasm.

I went with an excellent framework called yew, it’s very similar to ReactJS in a lot of way, and trunk to build the wasm/html.

Implementing the UI was very straight forward, just added an index.html and index.scss to the root of the project, and then created ./src/app.rs.

./src/app.rs (source)

#[function_component(App)]
pub fn app() -> Html {
	let input_ref = use_node_ref();
	let alphabet_value_handle = use_state(|| Vec::<(char, String)>::new());
	let alphabet_value = (*alphabet_value_handle).clone();

	let onchange = { .... }
	
	html! {
		<main>
			<label for="words">
				{"Words:"}
			</label>
			<input ref={input_ref} onchange={onchange} id="words" type="text"/>
			<br />
			<ul>
				{alphabet_value.iter().map(|(c,s)| html! { <li><pre>{"`"}{c}{"` -> "}{s}</pre></li> }).collect::<Html>()}
			</ul>
		</main>
	}
}

But I wanted to maintain the cli interface in the same program, after some googling and reading some rust docs, I ended up with:

./src/main.rs (source):

mod alphabet;

#[cfg(target_arch = "wasm32")]
mod app;
#[cfg(target_arch = "wasm32")]
fn main() 
	yew::Renderer::<app::App>::new().render();
}

#[cfg(not(target_arch = "wasm32"))]
fn main() {
	// skip first arg, which is the program's name
	for arg in std::env::args().skip(1) {
		println!("-> {} <-", arg);
		for (c, s) in alphabet::to_alphabet(arg) {
			println!("{} -> {}", c, s);
		}
	}
}

And tada https://oneofone.dev/alphabet/ was born.