without the unsafe block, what can possibly cause some kind of memory bug, or race condition, or the closest thing to ub.
my only very minor experience was having a for loop, loop through more elements than needed causing weird stuff, but that's obviously not something related to unsafe code
You can leak memory, use Mutexes to create a deadlock, or simply loop forever causing a function to never return.
If you consider a memory leak to be a memory bug, then this qualifies. This is not a "memory safety" problem, however, which is prevented by safe Rust.
A deadlock can be created by a race condition, so this qualifies. This is not a "data race", however, which is prevented by safe Rust.
A function that never returns probably doesn't qualify.
And you can of course, in perfectly safe code, still create logic errors, which make your program vulnerable to exploits. Which also is, you know, pretty bad.
Rust does not prevent logic bugs in general (though some APIs are designed to make doing the correct thing easier than shooting yourself in the foot). Your application hanging or crashing is among the more benign things since they're easy to notice. An application that keeps running but produces incorrect behavior can be far more dangerous.
A non-exhaustive list of things that are memory-safe as far as the language spec is concerned:
I once found I had written something that boils down to this:
fn bad(src_stream: &mut Stream, dst_stream: &mut Stream) -> Result<()> {
let mut message = [0u8; 256];
loop {
let length = src_stream.read(&mut message)?;
let sensitive = check_data(&message);
if !sensitive {
dst_stream.write(&message)?;
}
}
}
This can result in sensitive data being sent to the destination. When a long sensitive message is followed by a short non-sensitive message. The problem being that the write should have been:
dst_stream.write(&message[0..length])?;
This is not a memory safety bug or UB, it's a simple logic error. But when I discovered it, after the code had passed the tests in place, it felt like one of those buffer overrun kind of things one does in C.
I'm supprised no one has mentioned the possibility of stack overflow, for example, recursion level is too deep (or infinite), or create a value of very large type on the stack.
one of the most frustrating examples is that, rust does NOT guarentee (as I understand it) to elide the move from a temporary value on stack when creating a Box, and this can crash the program if the size of the type is very large. see e.g.
I should mention, this behavior is more commonly seen in debug builds, but they can appear in release builds too, when the use case is not as simple.
It's not unsafe to upload your bank password to pastebin, for example.
unsafe is about whether the code has defined behaviour where the logging makes sense, where you can use code inspection to think about what it's doing, etc. You can still do all kinds of horrible-for-a-user things without unsafe; it's just that it'll be what the code says to do.
I'm pretty sure from what the OP says in their question that they are well aware of the difference between the Rust definition of the unsafe keyword and the more general use of the word in security and safety critical software worlds etc.