When writing code in any programming language, choosing the appropriate constructs is crucial for creating readable and maintainable code, and in Rust is no different. In this post, I will discuss a specific dilemma I find myself thinking often about: deciding between using combinators or match clauses.
Let’s consider an example where in an application we need to retrieve the value of an environment variable, convert it to a map, and then serialize it to JSON. The first approach uses combinators and closures:
std::env::var("SELECTOR") .ok() .and_then(|env_var| selector_field(env_var.as_str()).ok()) .map(|sel_map| serde_json::to_value(sel_map).unwrap()) I ignore the declaration of the fn selector_field(&str) -> Option<BTreeMap<String, String> > function for simplification. This version is concise and elegant, but doesn’t log any warnings if and for what reason the result turns out to be None . To add some context and make it easier for debugging, we can log some useful information. We can modify the code like thi