I have never seen, other than like, methods on Result itself, Rust code that accepts a Result type as an argument. You are absolutely right that would lead to a crazy amount of clutter.
> Imagine you need to call f(h1()) and f(h2()) where h1() and h2() return the same data type for valid data but different types of errors. What do you do?
It sort of depends on the specific different error types. Most of the code I've been writing is application level code, so I'd just be returning the errors upstream. The anyhow library makes it easy to return a "trait object" which is sort of like a virtual base class in C++, at least for these purposes. With that I could just do
f(h1()?);
f(h2()?);
The ? operator lets you return the error half of the Result to the parent. If I had to do the conversions myself, if I implemented some traits, I could do this too. That said, I would probably use a temporary for clarify: let x = h1()?;
f(x);
let x = h2()?;
f(x);
For some cases, I may not want to implement the traits, but maybe there's some sort of common error that I'd want to return to the caller. In that case, I may let x = h1().map_err(|e| /* do whatever conversion here */ )?;
f(x);
let x = h2().map_err(|e| /* do whatever conversion here */ )?;
f(x);
These three represent the vast majority of this kind of code I write and see in the wild.