I find anything but a Result<T, E> ADT to be a poor solution. Errors shouldn't have language-level support (exceptions), and error codes are just a poor implementation of a result ADT - they cannot be enforced by the type system.
Result is a monad, so it doesn't require an alternative code path. It's the cleanest and safest.
"Exceptions" should always be for fatal, irrecoverable errors (array index out of bounds). I'm okay with having them, but I don't think languages should support "catch".