The question mark operator just gives you an easy way of doing perhaps the most common handling of errors, which is propagating it up to the caller to worry about. So somewhere along the way you’re going to need to actually handle it (or explicitly ignore it), though it’s also possible in simple programs to let Rust handle it, by having your main function return a Result (that is, something like `fn main() -> Result<(), Error>`), in which case if it’s an Err it prints the error (roughly `eprintln!("Error: {:?}", err);`) and exits with status code 1.
It’s up to the caller what to do with an error - panic, return it in turn, wrap it, or whatever else makes sense in context. It’s semantically quite similar to Go - except with better library support (Result instead of returning a tuple) and better syntax (the “?” operator instead of manual checks).
For truly exceptional situations, Exceptions are a good match because actually you wanted a control flow change. If the NetworkFailed maybe all of this complicated multi-node-synchronisation code is useless and we should handle that in code for NetworkFailedExceptions.
But one program's exceptional situation is another's business as usual, our desktop network visualisation icon does not want to eat a NetworkFailedException, the fact that NetworkFailed is just a reason to change the little icon to red instead of green, not exceptional at all.
This is particularly egregious when doing large data processing. If I doBackups() sending forty different sources to six backup servers, I actually don't want the failure of one backup server to handle one of the forty sources to blow up the entire function, I would much rather get back a detailed overview and go OK, 5 out of 6 ain't bad, no need to set off alarm bells and wake up the sysadmins. Error handling Go's way makes this a bit ugly but practical, Rust's way makes it feel reasonably ergonomic, in languages like C++ or Java it was kinda horrible (but C++ is slated to get an Expected class to mostly fix this in C++ 23).