It sometimes feels like there is a strange resistance to defining ad hoc sum types. If your language has good pattern matching capabilities and an expressive syntax for declaring data types, then this scales very well.
For example in a pseudolanguage:
data FooResult = Number | Error
def foo(n: Number): FooResult = ...
foo(42) match {
case n: Number => print(n + 1)
case e: Error => // handle error case
}
This gets you very far most of the time. If the tag overhead matters (which should largely only matter in a hot loop), then try to refactor so that the hot loop doesn't error out.Granted in many languages the above cannot be expressed succinctly and is often avoided for this reason.