I don't agree that people should avoid exceptions for expected errors. The idea that they should has twisted the error handling landscape into a pretzel and is responsible for some ugly bits of Rust design.
Exceptions are a mechanism for error handling that comes with control flow changes.
But Rust already has perfectly nice control flow mechanisms (look at std::ops::ControlFlow for example) and perfectly nice error handling. So you can use either, or both, as necessary, you don't need this particular Frankenstein's Monster.
Using Exceptions for things that aren't actually exceptional is perverse.
> Using Exceptions for things that aren't actually exceptional is perverse.
In python using exceptions for some control flow is actually accepted practice if you don't go overboard.
I think it's better that way because it turns them into something familiar instead of pushing them back to some extraordinary circumstances.
The thing that C++ does with exception means that programmers mind is encouraged to stay on happy path because when he'll walk off this path he'll have to deal with this exception handling monster.
So it's better to either not have exceptions and expose unhappy paths to programmer clearly like Rust or Go does, or familiarize exceptions and their handling so programmers can use them easily in all circumstances like in Python.
Just don't do what C++ and similar do. Offer them as a heavy and weird side mechanism reserved only for super exceptional cases.
C++ exception handling is nothing special, it’s just surrounded by a lot of FUD, premature optimization, outdated benchmarks and cargo cult practices. Like you say, limited exposure adds to all this mystery around it.
If you follow “rule of zero” they just work. The problems come when you start implementing your own destructors “to handle exceptions”, which unfortunately seem to be a very common practice in the wild.