Unsafe blocks in unsafe functions?

In the last days, I sometimes had a look into the book from the Brown University, mostly for their quizzes -- some are really quite difficult.

I just saw the following section in Unsafe Rust - The Rust Programming Language

To perform unsafe operations in the body of an unsafe function, you still need to use an unsafe block, just as within a regular function, and the compiler will warn you if you forget. This helps to keep unsafe blocks as small as possible, as unsafe operations may not be needed across the whole function body.

I thought that must be an mistake, but we have now the same in the original book.

I am quite sure that I read that unsafe blocks are not required in unsafe functions 18 months ago when I started seriously learning Rust -- but I might confuse it with another book, perhaps the one from Jim Blandy.

So what is the current state? Have unsafe blocks been necessary in unsafe functions in the very early days of Rust? Or are they again required with the latest compiler? Or is the official book just wrong? (Of course I know that I need an unsafe block to call an unsafe function.). Or, is it just good style to use unsafe blocks even inside of unsafe functions, even when it is not really required?

1 Like

They are not required, but there is the unsafe_op_in_unsafe_fn lint, which is warn by default since the 2024 Edition. See also:

4 Likes

As far as I can make out unsafe has at least two distinct meanings. For example:

  1. I have functions that take raw pointers as parameters. Almost certainly calling them is unsafe, the caller must ensure those pointers are valid, and they should be marked as such `pub unsafe f(self: *mut Self){...}"

  2. Within a function I may be doing unsafe things here and there and the unsafe things need to be delimited.

Of course that can lead to the situation where both the function needs to be unsafe and there will be unsafe blocks within it. And you start to think there are redundant unsafes. For example:

    pub unsafe fn push_back(self: *mut Self, elt: T) {
        unsafe {
            let node_size = core::mem::size_of::<Node<T>>() as size_t;
            let new_node = malloc(node_size) as *mut Node<T>;
            if new_node.is_null() {
                panic!("Failed to allocate memory for node");
            }

            // Initialize the new node
            (*new_node).element = elt;
            (*new_node).next = ptr::null_mut();
            (*new_node).prev = (*self).tail;

            // Update list pointers
            if (*self).tail.is_null() {
                // Empty list case
                (*self).head = new_node;
            } else {
                // Connect current tail to new node
                (*(*self).tail).next = new_node;
            }

            (*self).tail = new_node;
            (*self).len += 1;
        }
    }
1 Like

The two meanings are:

  1. unsafe fn tells the function's callers: "my API is unsafe, you must make sure to satisfy my preconditions"
  2. unsafe { ... } blocks tell the functions we are calling: "I know that your API is unsafe, and I have made sure to satisfy your preconditions"

These are two different API boundaries with different requirements, so it makes no sense that 1 implicitly does 2. Hence I recommend to #![deny(unsafe_op_in_unsafe_fn)] in your crates.

5 Likes

And the two meanings and two uses of unsafe will help you remember to provide Safety comments for both (unsafe fn: explain that these are my preconditions, unsafe block: explain how I satisfied your preconditions)

I've only read chapter 4, and this is off topic. But I'll throw out a warning here anyway: At least some of their quizzes give wrong, dangerous impressions about how one could write sound unsafe, by for example saying a certain snippet would not contain UB if it was allowed to compile, when it actually would contain UB. Please be aware of that and don't take their examples as gospel.[1]


  1. It's too out of cache for full details, but their examples do things like hyper focus on dynamic allocation and ignore mutation behind a &_, for instance. â†Šī¸Ž

2 Likes