To clarify, there are two places that you can use the `unsafe` keyword:
unsafe { /* arbitrary code in here */ } // applied to a block
unsafe fn foo() { /* function body */ } // applied to a function
It's the second form that is infectious. When applied to a function, that function can only be called from an unsafe block or another unsafe function. The use of this form is to say, "hey, as the author of this function, I have no ability to determine whether or not this function is safe to use; let the caller beware."
However if, as the author of a function, you can take steps to ensure that the function is used safely, then you can stop the infection with an unsafe block.
For illustration, let's do one of the things mentioned in the post. Here's a function that prints every element in a vector:
fn foo1(v: Vec<int>) {
let length = v.len();
let mut i = 0;
while i < length {
println!("{}", v[i]);
i += 1;
}
}
That `v[i]` in there is potentially doing bounds checking (in practice a simple case like this would get optimized away by LLVM, but let's ignore that), but note that this is superfluous because we've written this code such that `i` will always be a valid index. So let's turn off bounds checking by using the `unsafe_get` method mentioned in the OP:
fn foo2(v: Vec<int>) {
let length = v.len();
let mut i = 0;
while i < length {
println!("{}", unsafe { v.unsafe_get(i) });
i += 1;
}
}
Like I was saying above, `unsafe_get` is a function that is itself marked unsafe, because the author of `unsafe_get` knows that you can cause memory unsafety by passing in an invalid index. But because we've written this program carefully, we know that both foo1 and foo2 are exactly as safe, so we've fenced off the unsafety here with the `unsafe` block. Both foo1 and foo2 can be called from safe Rust code with no special consideration.
Now, what happens when, years down the road, someone who isn't paying attention accidentally modifies this function?
fn foo3(v: Vec<int>) {
let length = v.len();
let mut i = 0;
while i <= length { // look carefully
println!("{}", unsafe { v.unsafe_get(i) });
i += 1;
}
}
This version of the function contains an off-by-one error. Anyone calling foo3 will try to index beyond the end of the array, and all hell will break loose. What do you do then? Well, you grep for the word "unsafe" and you start auditing unsafe blocks, looking at all the inputs that you send into them. This is generally the same approach you'd take in any other systems language, except that by limiting where unsafe code can appear you reduce the code that can potentially go wrong by 10-100 times, and speed up your auditing commeasurably. And the promise made in the OP stands: if you don't yourself use `unsafe` code and memory safety happens, it's someone else's fault, not yours. :)
And how often do you need to resort to unsafe code in Rust? It depends on your domain. Efficient data structures often require all sorts of custom pointer tricks, so maybe a tenth to a third of their code is unsafe in Rust. For application-level programming, I'd expect no more than 1-3% unsafe code (maybe up to 5-10% if you're trying to push performance to the absolute limit, like Servo is doing).