Unsafe doesn't remove all the guard rails, but it allows you to do a handful (five) of tremendously
unsafe things, for which you then take all responsibility as the compiler cannot analyse them for safety.
* You can dereference raw pointers. Are they pointing at anything at all? At something that type pointer might reasonably point to? Not the compiler's job to check, if you screwed up your program now has Undefined Behaviour like C or C++
* You can access C-style unions. This one says it might be a boolean, a pointer to something, or an integer. You decided to guess it's... a boolean. If you're wrong you get Undefined Behaviour.
* You can mutate (change) static variables. Global cheese flavour is Cheddar? Let's change it to Gorgonzola, no wait Parmesan. Was anybody else using that for anything? No idea, too bad, you might introduce Undefined Behaviour if you didn't think this through.
* You can call functions Rust labelled "unsafe". These functions come with instructions about rules you must follow to use them safely. If you violate any of those instructions, all bets are off, but if you obey the instructions whoever wrote the function (which may be the Rust standard library) promises that was safe.
* You can implement special Traits labelled "unsafe" to implement. These Traits, unlike most Rust traits, have to promise they're correct. If you claim to implement Searcher, a Trait for text processing, and you get it wrong, other people's code may blow up. Whereas I have types like Funhouse<> which claim to implement the trait Eq (promising they understand how equality works) and then as a goof they utterly refuse to do so correctly, that's a safe trait, nothing blows up. My types don't work in a sensible way (Funhouses are simultaneously equal to and not equal to every other Funhouse including themselves, which is stupid), but your program doesn't have Undefined Behaviour if you use these types, they're just annoying.
But, lots of things that would cause Undefined Behaviour in some popular languages are impossible even in unsafe Rust.
Suppose I have a function that takes an array of 1024 bytes. I try to do { let z = array[1040]; } -- That won't compile, this array isn't big enough. If I write unsafe { let z = array[1040]; } it still doesn't compile, didn't I get it, this array isn't big enough.
OK, let's have the function take a parameter k, and at runtime we can set k to 1040 and try that way. If I write { let z = array[k]; } when k is 1040 the program panics. The array isn't big enough. If I write unsafe { let z = array[k]; } it still panics and the compiler even warns me that unsafe isn't doing anything for me here and I should remove it.
Similarly although Rust provides checked arithmetic (ie you can explicitly say "Tell me the answer to x + 100, or, if that would overflow, tell me it didn't work") its default integer arithmetic will panic in debug builds if you overflow and, if you missed that in debug, but it happens in production, you get overflow, which may very well not be what you wanted, but you apparently didn't even know it could happen, so, good luck with that. Either way, no Undefined Behaviour. 255_u8 + 255_u8 is a compile error, a runtime panic or at worst it's 254, but it definitely is not zero, forty-two, or unrelated program misbehaviour.