0

In the crate I'm developing I have several unsafe functions, which are marked as such because of reasons explained in this answer. In unsafe functions, I can perform unsafe operations as if the full function body was wrapped in an unsafe { } block.

The problem is that, in bigger functions, only a small part of the function body is actually performing unsafe operations, while the rest is doing perfectly safe stuff. Often, this safe stuff is even pretty independent of the unsafe code. In these larger functions, I would like to narrow the scope of unsafe operations. The reason should be fairly understandable: I also don't wrap my complete codebase in an unsafe { } block just because I can.

Unfortunately, there isn't a safe { } block to "invert" the behavior of unsafe functions. If there were I would use it like that:

unsafe fn my_function() {
    safe {
        // ... doing safe stuff ...

        unsafe {
            // ... doing `unsafe` stuff ...
        }

        // ... doing safe stuff ...
    }
}

But as this is not possible: what are best practices in these situations to narrow the scope of unsafe operations? Are there established tricks to deal with this?

Just to be clear: this question is not about discussing whether or not narrowing the unsafe scope is good or bad. I stated that I want to do it: this question is about how to do it and what solutions (if any) are most commonly used in practice. (And if you don't understand why I would like to do it, this RFC is very related.)

Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
  • 1
    Just to clarify, in your case *calling the function itself* isn’t always safe, correct? – Konrad Rudolph Aug 16 '19 at 09:52
  • @KonradRudolph Yes exactly. It is marked `unsafe` because its safety depends on input parameters or global state. As explained [here](https://stackoverflow.com/a/29050417/2408867). So yes, I'm pretty sure it actually has to be an `unsafe` function. – Lukas Kalbertodt Aug 16 '19 at 09:53
  • This is more of a random thought than an answer, which is why I only comment, as feature itself is unstable, but you can label blocks and in turn mark them as whatever you want really, which looks great, for now, of course, just follow the answers because they're a lot better than switching to nightly just for [this](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=bf08fa9d3175b0147809690187403e41)... It's also technically abuse of feature, but I think it would convey the intention pretty well. –  Aug 17 '19 at 10:21

1 Answers1

3

If you want to use the unsafe keyword as a way to catalogue all unsafe operations, you can construct more accurate boundaries by splitting your code into safe private functions. I'm not sure if it exactly meets your requirement of "best practice" since I don't know of any large projects using the technique, but it will work:

// Document the assumptions of this unsafe function here
pub unsafe fn my_function() {
    my_internal_function()
}

// private
fn my_internal_function() {
    // ... doing safe stuff ...
    unsafe {
        // Document the assumptions of this unsafe block here
        // ... doing `unsafe` stuff ...
    }
    // ... doing safe stuff ...
}

If you are concerned about the existence of a "safe" function that is actually unsafe to use, introducing a risk of accidentally being used incorrectly, you can nest those private functions so they are not callable outside the main unsafe function:

pub unsafe fn my_function() {
    fn my_internal_function() {
        // ... doing safe stuff ...
        unsafe {
            // Document the assumptions of this unsafe block here
            // ... doing `unsafe` stuff ...
        }
        // ... doing safe stuff ...
    }

    my_internal_function();    
}

After all of this, properly documenting the assumptions of unsafe code with comments is the most important part to get right. This sort of trick will only help if you are concerned about metrics for the number of unsafe lines.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204