4 ms·
That may work for most cases (and is what I myself often do), but it's worth keeping in mind that it won't work in general. There are many situations where clos
by Arnavion 6y ago
That may work for most cases (and is what I myself often do), but it's worth keeping in mind that it won't work in general. There are many situations where closures are not a replacement for imperative blocks, because the compiler has an understanding of exclusive paths in imperative control flow that does not apply to closures. Eg:
fn foo(cond: bool, s: String) {
if cond {
drop(s);
}
else {
drop(s);
}
}
... compiles file even though `s` is being consumed in two different locations, because the compiler is aware of the control flow of if-then-else so it knows only one arm can be executed.
If you tried to replace the bodies of the two branches with closures, it would not compile, because both need to consume the String. That is,
fn if_(cond: bool, t: impl FnOnce(), f: impl FnOnce()) { if cond { t() } else { f() } }
fn foo(cond: bool, s: String) {
if_(cond, || drop(s), || drop(s));
}
... won't compile. Even though only one of the two closures will be executed, both closures need to be created regardless, which means both need to own the String, which is not allowed.
This problem came up all the time in pre-async-await futures 0.1 code because async code had to be written with futures combinators that all used closures, so even in cases where the programmer knows only one closure will be invoked, they would still need to clone their values so that each closure could own them. Even in regular non-async code, it often comes up with the combinators like `Result::map_err`, like:
enum Error {
Io { path: PathBuf, inner: std::io::Error },
Deserialize { path: PathBuf, inner: serde_yaml::Error },
}
fn read_config(path: PathBuf) -> Result<Config, Error> {
let f = std::fs::File::open(&path).map_err(|err| Error::Io { path: path.clone(), inner: err })?;
let c = serde_yaml::from_reader(&f).map_err(|err| Error::Deserialize { path, inner: err })?;
Ok(c)
}
Notice the `path.clone()` in the first Result's map_err closure, which is needed even though the programmer knows there is nothing else that will use `path` once that statement executes. That's because, once again, both `map_err` closures need to be created regardless of whether they execute or not, so for the second closure to be created it must own `path`, and thus the first closure needs to clone it. The equivalent code with explicit `match` statements for each Result expr would not need to clone. Compare `read_config` and `read_config2` in https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3effc12ad19b9da8573c4d22fef45bb8 https://play.rust-lang.org/?version=stable&mode=debug&editio...
- zozbot234 6y agoThis seems to be a limitation of function call argument syntax, which does not distinguish "all of A, B, C..." vs. "callee picks one of A, B, C...". (Note that this is not the same as passing an "enum" as a function argument, which is more like "caller picks one of A, B. C... (and callee dispatches on what caller has chosen)".) Perhaps a general solution is feasible here.