How rustc handle unsafe Rust

Hi, I'm reading the rustc book right now. But after looking at the rustc book, I'm still confused about the handling of unsafe rust. As described in the book, rustc's main job is to ensure that unsafe rust can only be used in unsafe blocks, and that a warning lint is reported if redundant unsafe blocks are used.

The question is: I still don't understand how unsafe rust operations bypass rust's memory-security guarantees. I do not find that the rustc compiler relaxes borrowing checks for unsafe blocks or unsafe fn. So why does an unsafe operation bypass rustc's memory security check?

One question I'm more interested in is if I overuse an unsafe block of code, as follows:

pub fn grow(&mut self, new_cap: usize) {
        unsafe {
            let (ptr, &mut len, cap) = self.triple_mut();
            let unspilled = !self.spilled();
            assert!(new_cap >= len);
            if new_cap <= self.inline_size() {
                if unspilled {
                    return;
                }
                self.data = SmallVecData::from_inline(mem::uninitialized());
                ptr::copy_nonoverlapping(ptr, self.data.inline_mut().ptr_mut(), len);
            } else if new_cap != cap {
                let mut vec = Vec::with_capacity(new_cap);
                let new_alloc = vec.as_mut_ptr();
                mem::forget(vec);
                ptr::copy_nonoverlapping(ptr, new_alloc, len);
                self.data = SmallVecData::from_heap(new_alloc, len);
                self.capacity = new_cap;
                if unspilled {
                    return;
                }
            }
            deallocate(ptr, cap);
        }
    }

As you can see, I put a lot of code into an unsafe block that could be executed without the unsafe block. Are there any practical problems with this (performance or security)? In addition to affecting code readability.

First, this is more of a https://users.rust-lang.org/ question - internals is more for cases where you're discussing things that are likely to need a change to the Rust compiler or standard libraries.

unsafe by itself does not bypass any checks. It "merely" permits you to use the "unsafe superpowers"; these are operations which have preconditions you must meet that the compiler cannot verify for you. For example, if you dereference a raw pointer, you are on the hook for ensuring that the "shared or mutable, not both" property is met by the operation, and the compiler cannot help you.

If you don't use any of the unsafe superpowers, then unsafe does nothing. If you do use any of them, you are responsible for making sure that you meet the preconditions set by those functions; a failure to meet those preconditions results in anything from breaches of memory safety at the better end through to an ill-formed program without a diagnostic (where the output binary can be completely unrelated to your source code) at the worst end. The Rust team don't deliberately maximize the damage from a failure to meet preconditions, but they can't easily constrain it either.

The only problem with an excessively large unsafe block is that you've made it harder to read the code, and hence to verify that all the uses of unsafe superpowers correctly check their preconditions. Additionally, it's now easy to accidentally make use of an unsafe superpower in that block during refactoring, and not realise that you introduced a bug by doing so.

As a result, general advice is to keep unsafe blocks as small as is reasonable, because then it's clear which bits have preconditions that need checking, and which bits don't.

4 Likes

Note this question was asked and answered on Zulip.

3 Likes