While I am sure you could abuse unsafe and make your code as liable to crash as C, best practice is to avoid unsafe at all costs and only use if it *must* be used. And if your code *does* crash, then the first course of action is grep for "unsafe" and most likely one of those blocks is the culprit.
And if you're seeing unsafe in packages then most likely it is because that's what they're doing. Even a few unsafe blocks is infinitely preferable to everything being unsafe. As an example I developed a project over 3 years with tens of thousands of lines and a couple of unsafes. It crashed once. That was due to me calling an openssl function with the wrong arguments. Since I only had a few unsafe blocks it was very easy to identify the issue and fix it.
One misconception is that unsafe is throwing away all the safety guarantees that normal Rust provides, but it doesn't. You still get borrow & lifetime checking within the unsafe block. So unsafe Rust is still safer than C or C++.
But at the same time it's also a very poor choice of language to actually write anything substantial in. Performance blows and it doesn't scale. Multithreading is especially bad and even if they fix that, code is liable to break in all sorts of racy ways due to the body of code out there. Package management is a disaster with workarounds using virtual environments and other nonsense to get around the globalness of packages. Typing (or rather hinting) is an afterthought and a lot of code barely bothers with it. Lack of strong types bleeds into truthy/falsely, e.g. empty arrays being considered False.
What gets me is Python being most popular for LLM programming when it is possibly the slowest mainstream language in existence. AI is probably bolstering Python's popularity but really something like Rust would be far more suitable for that purpose.
The trouble with the rat-race is that even if you win, you're still a rat. -- Lily Tomlin