Sometimes there are many ways for a program to phrase a message containing a dynamic value to its users. For instance:
Not all of the messages contain the value as a mere prefix or suffix. In a dynamic language, this would've seemed the logical task for string formatting.
For media where repetitiveness is undesirable (e.g. Slack channels) there are so many different phrasings that producing each final String to output using something like:
pub fn h(x: usize) -> String {
rand::sample(rand::thread_rng(), vec![
format!("{} minutes remain.", x),
format!("Hurry up; only {} minutes left to finish.", x),
format!("Haste advisable; time ends in {}.", x),
/* (insert many more elements here) */
], 1).first().unwrap_or(format!("{}", x))
}
Would be:
format!(/*...*/, x) each time.Is there any way to avoid these shortcomings?
Were it not for the compile-time evaluation of format strings, a function returning a randomly-selected &'static str (from a static slice) to pass into format!, would have been the preferred solution.
Rust supports defining functions inside functions. We can build a slice of function pointers, have rand::sample pick one of them and then call the selected function.
extern crate rand;
use rand::Rng;
pub fn h(x: usize) -> String {
fn f0(x: usize) -> String {
format!("{} minutes remain.", x)
}
fn f1(x: usize) -> String {
format!("Hurry up; only {} minutes left to finish.", x)
}
fn f2(x: usize) -> String {
format!("Haste advisable; time ends in {}.", x)
}
let formats: &[fn(usize) -> String] = &[f0, f1, f2];
(*rand::thread_rng().choose(formats).unwrap())(x)
}
This addresses the "wasteful" aspect of your original solution, but not the "tedious" aspect. We can reduce the amount of repetition by using a macro. Note that macros defined within a function are local to that function too! This macro is taking advantage from Rust's hygienic macros to define multiple functions named f, so that we don't need to supply a name for each function when using the macro.
extern crate rand;
use rand::Rng;
pub fn h(x: usize) -> String {
macro_rules! messages {
($($fmtstr:tt,)*) => {
&[$({
fn f(x: usize) -> String {
format!($fmtstr, x)
}
f
}),*]
}
}
let formats: &[fn(usize) -> String] = messages!(
"{} minutes remain.",
"Hurry up; only {} minutes left to finish.",
"Haste advisable; time ends in {}.",
);
(*rand::thread_rng().choose(formats).unwrap())(x)
}
My suggestion would be to use a match to avoid unnecessary computation and keep the code as compact as possible:
use rand::{thread_rng, Rng};
let mut rng = thread_rng();
let code: u8 = rng.gen_range(0, 5);
let time = 5;
let response = match code {
0 => format!("Running out of time! {} seconds left", time),
1 => format!("Quick! {} seconds left", time),
2 => format!("Hurry, there are {} seconds left", time),
3 => format!("Faster! {} seconds left", time),
4 => format!("Only {} seconds left", time),
_ => unreachable!()
};
(Playground link)
Admittedly, it's a little bit ugly to match numbers literally, but it's probably the shortest you can get it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With