While funny, this also highlights part of why I like rust's error handling story so much: You can really just read the happy path and understand what's going on. The error handling takes up minimal space, yet with one glance you can see that errors are all handled (bubbled up in this case). The usual caveats still apply, of course ;)
If the matches are causing too much nesting/rightward drift, then that could be an indicator that you're doing something wrong.
If it's the opposite, then you're probably doing something right, except maybe the code needs some refactoring if there is too much clutter.
If there isn't much difference, then it's a matter of style. I for example sometimes prefer to match on bools in some contexts because it makes things look clearer to me, despite it being not the recommended style. I'm also a proud occasional user of bool::then() and bool::then_some() 😉
Also, if you find yourself often wishing some API was available for types like bool, Option, and Result, then you don't have to wish for long. Just write some utility extension traits yourself! I for example have methods like bool::err_if(), bool::err_if_not(), Option::none_or_else(), and some more tailored to my needs methods, all available via extension traits.
Macros can also be very useful, although some people go for them too early. So if everything else fails to declutter your code, try writing a macro or two.
And it's worth remembering, there is no general rule, other than if the code is understandable for you and works, then you're probably okay irregardless of style. It's all sugar after all, unless you're really doing some glaringly wrong stuff.
// Add `as_cstr()` to `NixPath` trait first
let some_or_null_cstr = |v| v.map(NixPath::as_cstr)
.unwrap_or(Ok(std::ptr::null()));
// `Option::or_null_cstr()` for `OptionᐸTᐳ`
// where `T: NixPath` would make this even better
let source_cstr = some_or_null_cstr(&source)?;
let target_cstr = target.as_cstr()?;
let fs_type_cstr = some_or_null_cstr(&fs_type)?;
let data_cstr = some_or_null_cstr(&data)?;
let res = unsafe { .. };
Edit: using alternative chars to circumvent broken Lemmy sanitization.
I think the issue with this is that the code (https://docs.rs/nix/0.27.1/src/nix/lib.rs.html#297) allocates a fixed-size buffer on the stack in order to add a terminating zero to the end of the path copied into it. So it just gives you a reference into that buffer, which can't outlive the function call.
They do also have a with_nix_path_allocating function (https://docs.rs/nix/0.27.1/src/nix/lib.rs.html#332) that just gives you a CString that owns its buffer on the heap, so there must be some reason why they went this design. Maybe premature optimization? Maybe it actually makes a difference? 🤔
They could have just returned the buffer via some wrapper that owns it and has the as_cstr function on it, but that would have resulted in a copy, so I'm not sure if it would have still achieved what they are trying to achieve here. I wonder if they ran some benchmarks on all this stuff, or they're just writing what they think will be fast.
As someone mentioned in the linked thread, the problem is the bad design of those with... functions, that take a closure instead of just returning a value.
Based on the source https://docs.rs/nix/latest/src/nix/lib.rs.html#297, it seems it was done this way to ensure correct lifetime for the allocated buffer? Which could have been achieved by just returning a wrapper that owns it.
the issue is that you can’t return from inside a closure, since the closure might be called later/elsewhere
That would only solve the stacking of the ?, but not the ugly nesting.
Still, I wonder why more language don't have a feature that Kotlin has, where some functions, that take lambdas as arguments, can be explicitly marked as inline. That then inlines both the function and the passed lambda into the call site. As a result, an inline function can do nothing with lambda, other than call it from its normal flow (i.e. it can't store it somewhere else, or pass it to a non-inline function, etc.). This then allows non-local returns, https://kotlinlang.org/docs/inline-functions.html, https://kotlinlang.org/docs/inline-functions.html#non-local-returns.