If you look at how POSIX does it, pretty much every single function has error codes, signaling everything from lost connections, to running out of memory, entropy or whatnot. Failures are hard to abstract away. Unless you have some real viable fallback to use, you're going to have to tell the user that something went wrong and leave it up to them to decide what the application can best do in this case.
So in your case, I would return Result<T>, and encode the errors in that. Simply expose the problem to the caller.
1. Have a constraint on T that lets you return some sort of placeholder. For example, if you've got an array of u8, maybe every read past the end of the array returns 0.
fn bar<T: Default>() -> T {
if let Some(t) = foo() {
t
} else {
eprintln!("programmer error, foo() returned None!");
Default::default()
}
}
2. Return a `Option<T>` from bar, as you describe.3. Return a `Result<T, BarError>`, where `BarError` is a struct or enum describing possible error conditions.
#[non_exhaustive]
enum BarError {
FooIsNone,
}
fn bar<T>() -> Result<T, BarError> {
if let Some(t) = foo() {
Ok(t)
} else {
eprintln!("programmer error, foo() returned None!");
Err(BarError::FooIsNone)
}
}