Okay, there are 5 snippets from ripgrep crate, with different representations:
1
imperative (original)
/// Returns the relevant CPU features enabled at compile time.
fn compile_cpu_features() -> Vec<&'static str> {
let mut features = vec![];
if cfg!(feature = "simd-accel") {
features.push("+SIMD");
} else {
features.push("-SIMD");
}
if cfg!(feature = "avx-accel") {
features.push("+AVX");
} else {
features.push("-AVX");
}
features
}
declarative (current Rust)
/// Returns the relevant CPU features enabled at compile time.
fn compile_cpu_features() -> Vec<&'static str> {
vec![
if cfg!(feature = "simd-accel") { "+SIMD" } else { "-SIMD" },
if cfg!(feature = "avx-accel") { "+AVX" } else { "-AVX" },
]
}
declarative (proposal)
/// Returns the relevant CPU features enabled at compile time.
fn compile_cpu_features() -> Vec<&'static str> {
vec![
cfg!(feature = "simd-accel") && "+SIMD" or "-SIMD",
cfg!(feature = "avx-accel") && "+AVX" or "-AVX",
]
}
My opinion is that the latter has better semantics, since it reads more like “vec of [feature, …]” and less like “vec of [expression returning feature, …]”.
It’s more “compressed” and less “flexible”, but on this snippets I think that’s not required.
2
imperative (original)
let hash = match revision_hash.or(option_env!("RIPGREP_BUILD_GIT_HASH")) {
None => String::new(),
Some(githash) => format!(" (rev {})", githash),
};
declarative + lazy (current Rust)
let hash = revision_hash.or_else(|| option_env!("RIPGREP_BUILD_GIT_HASH"))
.map(|githash| format!(" (rev {})", githash))
.unwrap_or_else(String::new);
declarative + lazy (proposal)
let hash = (revision_hash or option_env!("RIPGREP_BUILD_GIT_HASH"))
.map(|githash| format!(" (rev {})", githash))
or String::new();
I think that the last is better, because it encourages (in theory) writing lazy evaluating code by default.
But it could be a bit hard to read due to braces on the first line.
As a possible improvement we could consider a syntax similar to the following:
let hash = revision_hash or { option_env!("RIPGREP_BUILD_GIT_HASH") }
.map(|githash| format!(" (rev {})", githash))
or { String::new() };
3
imperative (original)
if early_matches.is_present("trace") {
log::set_max_level(log::LevelFilter::Trace);
} else if early_matches.is_present("debug") {
log::set_max_level(log::LevelFilter::Debug);
} else {
log::set_max_level(log::LevelFilter::Warn);
}
declarative (current Rust)
log::set_max_level(
if early_matches.is_present("trace") {
log::LevelFilter::Trace
} else if early_matches.is_present("debug") {
log::LevelFilter::Debug
} else {
log::LevelFilter::Warn
});
declarative (proposal)
log::set_max_level(
early_matches.is_present("trace") && log::LevelFilter::Trace
or early_matches.is_present("debug") && log::LevelFilter::Debug
or log::LevelFilter::Warn
);
The last example seems to be cleaner, since it has “map condition to type” semantics, and each “mapping” is placed on its own line.
However, this semantics is not really explicit. But I think that this could be fixed with (optional?) leading or:
log::set_max_level(
or early_matches.is_present("trace") && log::LevelFilter::Trace
or early_matches.is_present("debug") && log::LevelFilter::Debug
or log::LevelFilter::Warn
);
4
declarative (original)
Ok(if self.matches().is_present("type-list") {
Command::Types
} else if self.matches().is_present("files") {
if one_thread {
Command::Files
} else {
Command::FilesParallel
}
} else if self.matches().can_never_match(self.patterns()) {
Command::SearchNever
} else if one_thread {
Command::Search
} else {
Command::SearchParallel
})
declarative (proposal)
Ok(
self.matches().is_present("type-list") && Command::Types
or self.matches().is_present("files") && (one_thread && Command::Files or Command::FilesParallel)
or self.matches().can_never_match(self.patterns()) && Command::SearchNever
or one_thread && Command::Search
or Command::SearchParallel
)
Personally I don’t like nesting here. But I think that this code is still readable, and leading or also could improve this example:
Ok(
or self.matches().is_present("type-list") && Command::Types
or self.matches().is_present("files") && (one_thread && Command::Files or Command::FilesParallel)
or self.matches().can_never_match(self.patterns()) && Command::SearchNever
or one_thread && Command::Search
or Command::SearchParallel
)
5
declarative (original)
pub fn stdout(&self) -> cli::StandardStream {
let color = self.matches().color_choice();
if self.matches().is_present("line-buffered") {
cli::stdout_buffered_line(color)
} else if self.matches().is_present("block-buffered") {
cli::stdout_buffered_block(color)
} else {
cli::stdout(color)
}
}
declarative (proposal)
pub fn stdout(&self) -> cli::StandardStream {
let color = self.matches().color_choice();
self.matches().is_present("line-buffered") && cli::stdout_buffered_line(color)
or self.matches().is_present("block-buffered") && cli::stdout_buffered_block(color)
or cli::stdout(color)
}
I’d say that the second example also represents a case where code with or would be more straightforward.
Leading or version:
pub fn stdout(&self) -> cli::StandardStream {
let color = self.matches().color_choice();
or self.matches().is_present("line-buffered") && cli::stdout_buffered_line(color)
or self.matches().is_present("block-buffered") && cli::stdout_buffered_block(color)
or cli::stdout(color)
}