There's at least one language, Koka (https://koka-lang.github.io/koka/doc/book.html) that has effects in its type system. There are probably others. If you don't have effects, you could use union types.
In rust, you write "f(g()?)", and make sure that your function body returns a Result<> with an error type that g()'s error type can be converted to.
It works great. Also, note that f() doesn't care about the type of error returned by g(), and that it will be a compilation error if g()'s error type turns into something incompatible.
Sadly, there are proposals to add exceptions to rust, and it seems likely they will be accepted, breaking error handling semantics across the entire ecosystem (even in new code, since exceptions are too hard to use correctly).
AFAIK, the only proposal related to exceptions is adding a `try` block that would scope the `?` operator to that block instead of the current function.
You can – by making f accept g's return types, including the error. This is even being done in the Go standard library: https://pkg.go.dev/text/template#Must
That’s interesting, but consider two functions like func ParseInt(string) (int, error) and func Abs(int) int. You lose some composability when using errors over exceptions. The Rust solution mentioned elsewhere seems elegant.