Are there any specific rules regarding the choice between "unsafe fn" and "unsafe block"?

I started moving from C++ to Rust recently. I’m not sure about the unsafe usage style.

You see, Rust provides “unsafe fn” and “unsafe block”. If not for FFI or unsafe trait, I can simply wrap the whole implementation of an unsafe function to avoid the unsafe tag before function. I think this is OK because the callers will not bother adding “unsafe” to their code.

But as I learn from many Rust projects, I find that there are so many functions marked as unsafe instead of using unsafe block. This is quite strange to me as they seem to neglect a safe wrapper. Are there any specific rules regarding the choice between “unsafe fn” and “unsafe block”?

1 Like

Unsafe code must uphold the guarantee that safe Rust code cannot cause undefined behavior.

This means any function that relies on its callers to maintain invariants that are necessary for memory safety must be an unsafe fn. And conversely, a safe fn must not cause undefined behavior, no matter how or when it is called.

Section 1.1 of the Rustonomicon explains this at greater length. (See also section 1.3, which explains how this interacts with private functions and module boundaries.)

4 Likes

To summarize the above:
Use this:

fn foo() {
    unsafe {
        /**/
    }
}

When you can assure the caller that the content of the unsafe block is safe, and use this:

unsafe fn foo2() {
    /**/
}

When you can’t, because this forces the user to open their own unsafe block:

fn main() {
    unsafe {foo2()};
}

To be able to call it, further showing them that foo2 might be unsafe if used improperly.

2 Likes

As an exaggerated example, imagine

fn deref_read<T> (ptr: *const T) -> T
{
    unsafe { core::ptr::read(ptr) }
}

fn deref_write<T> (ptr: *mut T, value: T)
{
    unsafe { core::ptr::write(ptr, value) }
}

then I would never need to use unsafe in Rust ever again. Fo instance, I would also be able to define:

fn convert<T, U> (value: T) -> U
{
    let converted = deref_read(&value as *const T as *const U);
    core::mem::forget(value);
    converted
}

fn force_drop<T> (value: *mut T)
{
    let _ = deref_read(value as *const T);
}

// etc.

and, this way, unsafe would forever be erradicated from Rust … since everything has (silently) become unsafe.


I hope this shows why keeping unsafe code so explicitly tagged is paramount for the lack of such a tag to mean anything / have any value.

4 Likes

Thank you for your links. I think it’s time to dive into unsafe part of Rust.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.