Using unsafe is like providing a "proof" that the code you write won't cause safe Rust to exhibit undefined behavior.
Writing unsafe requires a certain degree of skill, because one needs a deep understanding about what guarantees safe Rust provides, to ensure that your abstraction does not break them.
If you read the standard library docs, every unsafe block has a comment explaining / proving why it can't cause safe Rust code to exhibit undefined behavior.
Many of these blocks, particularly those in libcore, have been proved formally correct in proof assistants.
There is a theorem that proves that "if a safe Rust wrapper over unsafe code is proven correct, then extending safe Rust with that wrapper is still sound".
So you basically can infinitely extend the safe Rust subset of Rust with these abstractions.
> But if “unsafe” is used, can we still claim that Rust code is safe?
So the answer to this is "no, you can't claim that, you have to actually go and prove it".
Often, very often actually, the proofs are trivial.
For example, to index into a slice:
fn index_slice(slice: &[T], i: usize)
// SAFETY: if the index is not in bounds, we panic
assert!(i < slice.len());
unsafe {
*slice.as_ptr().add(i)
}
}
suffices.
Why? Because code that creates a slice with a ptr and a len field that do not point to a valid allocation with len valid elements already exhibits UB. That is, for any valid Rust program, we are guaranteed here that these two fields are "ok".
So the only thing we need to make sure of is that the index "i" is inbounds, and that's trivial to do with an assert that panics if this is not the case. That is, at runtime, no program for which the index is not inbounds will reach the ptr.add(i) method, so there is no way to offset this pointer out of bounds, much less dereference it.
Basically, because all safe Rust code can rely on all other safe Rust code upholding the rules, most of the proofs are really easy. The fact that you can prove safe abstractions over unsafe code in isolation from other abstractions is one of the main features of Rust.
---
For synchronization primitives, proving the absence of data-races is particularly hard, and requires a lot of expertise. The subset of Rust programmers that actually need to do this is very small. Most people just use those primitives that have already been proven today, of which there are many.