The difference is that when you are forced to declare where your program does not follow some safe semantics, while it's still trivial to write an overflow, it's also trivial (or at least not nearly as complex as some alternatives) to find and fix, compared to a language where the entire code base is a possible source for those problems.
For comparison, I would say C and C++ (but to a slightly lesser degree) are also trivial to write buffer overflows in, it is definitely non-trivial in many cases to find and fix those problems, as the surface area is so large.
To illustrate this, it's trivial to write a buffer overflow in almost all languages that provide any sort of raw memory access or hardware instructions (ASM), even if through a module. It's trivial for me to create a buffer overflow in Python or Perl my including a module that allows direct assembly code access. The difference when using those languages is that when I run into a buffer overflow problem, I can be reasonably sure it's in a place where I bypassed the assurances provided by those languages, or in some included module that did the same (including just using a shared library). When tracking down a problem like that, I'll focus my attention on those spots, which makes identifying and hopefully fixing those problems easier.
Rust's "unsafe" is really an in-language way to provide a similar set of levels of assurance about how likely the code is to have problems of specific types, with assurances provides by the language and compiler. This is a step above what C and C++[1] provide, and thus welcome.
1: C++ provides some similar assurances based on types, but by nature of how "safe" code becomes "unsafe" when used in conjunction with unsafe portions, is much harder to assess at a block level, since you can't even be sure a line is safe until you've verified all the types used within it.
For comparison, I would say C and C++ (but to a slightly lesser degree) are also trivial to write buffer overflows in, it is definitely non-trivial in many cases to find and fix those problems, as the surface area is so large.
To illustrate this, it's trivial to write a buffer overflow in almost all languages that provide any sort of raw memory access or hardware instructions (ASM), even if through a module. It's trivial for me to create a buffer overflow in Python or Perl my including a module that allows direct assembly code access. The difference when using those languages is that when I run into a buffer overflow problem, I can be reasonably sure it's in a place where I bypassed the assurances provided by those languages, or in some included module that did the same (including just using a shared library). When tracking down a problem like that, I'll focus my attention on those spots, which makes identifying and hopefully fixing those problems easier.
Rust's "unsafe" is really an in-language way to provide a similar set of levels of assurance about how likely the code is to have problems of specific types, with assurances provides by the language and compiler. This is a step above what C and C++[1] provide, and thus welcome.
1: C++ provides some similar assurances based on types, but by nature of how "safe" code becomes "unsafe" when used in conjunction with unsafe portions, is much harder to assess at a block level, since you can't even be sure a line is safe until you've verified all the types used within it.