Implementing unsafe fn in trait with a safe fn

Given a trait with an unsafe fn member, why is it not possible to write an impl for this trait where the function is not marked unsafe? I see unsafe as a requirement for the caller of the trait function, not as a requirement for the implementer.

Suppose I have a trait with a function that is in general unsafe to call, but a particular implementation of this function is perfectly safe. I would prefer to not mark this function implementation unsafe so as to avoid mistakenly writing unsafe code in it.

Unfortunately this isn't possible currently; you have to acknowledge the trait signature as written, and that includes unsafe.

In general, I don't know if a safe trait can soundly have an unsafe method; what does the caller know about calling the method if it can't trust implementations to behave sanely?

The exact semantics of an unsafe method on a safe trait is unclear, so in general I'd recommend avoiding it.

If you want to require unsafe blocks within an unsafe fn, the current way to do so is to define a safe function which it delegates to. This can be done within the outer unsafe fn declaration to closely scope the code.

unsafe fn example() {
    fn example_() {
        // use of unsafe fn is a compile error here
    }
    example_()
}

This is a great tip, thank you.

It's an interesting point. How is it different from a normal unsafe function though? An unsafe function relies on documentation to specify the preconditions to safely using it, and the same would be true for an unsafe method in a trait: all implementors would have to adhere to whatever is documented. Do you think that this extra requirement means the trait should be unsafe as well?

The difference is the distance between the docs and the implementation.

With a regular free unsafe fn, the declaration and docs are in the same location as the implementation. With trait methods, the declaration and docs are distinct from the many potential implementations.

I think the semantics of a safe trait implementation of an unsafe method would be that the documented preconditions are required to hold, and execution soundness must only at maximum rely on those preconditions, but the caller may not assume anything about the postcondition state.

So for example, a safe trait unsafe function given a pointer parameter guaranteed to be writable could write complete garbage to the pointer, no matter the docs.

But the ambiguity of the interpretation means that it's probably a bad idea in practice. The unsafe trait encodes that the implementation must be correct to the docs to uphold soundness. A safe trait is allowed to do any safe/sound action on its method's inputs, no matter the docs.

Or easier: use the #[require_unsafe_in_body] proc macro attribute. The more users using this crate, the more likely it is that a future RFC (for a future Rust edition) that made unsafe fns still require unsafe {} blocks inside it, be accepted.


The way I see it, if a non-unsafe trait has an unsafe method that can be overriden, then it is unsafe to call such method in a generic context.

trait Trait { unsafe fn unsafe_function (...args) -> Ret; }

unsafe // <---------------------------------------------------+
fn generic_function<T : Trait> (...args) -> Ret            // |
{                                                          // |
    // Safety? the safety contract of unsafe_function depends |
    // on T, thus requiring that this whole function          |
    // be marked unsafe --------------------------------------+
    <T as Trait>::unsafe_function(...args)
}

Indeed, someone would otherwise be able to do:

struct Evil;
impl Trait for Evil {
    unsafe
    fn unsafe_function (...args) -> Ret { ::std::hint::unreachable_unchecked() }
}

and it shouldn't then be possible to call such function from within non-unsafe code.

Conclusion

If the method can be overriden, and the trait is not itself unsafe (so as to put the burden of correctness on the implementor of the trait), then it must always be unsafe to call this function in a generic context.

So the only way to get such an unsafe associated function be callable from within a non-unsafe fn without breaking soundness, all the implementations of the function must be known:

trait Trait : TraitExt {
    // (non-`unsafe`) items 
}
trait TraitExt {
    unsafe
    fn associated_function (...args) -> Ret;
}

impl<T : ?Sized + Trait> TraitExt for T {
    unsafe
    fn associated_function(...args) -> Ret
    {
        // sole implementation, allowed to use the non-unsafe items
    }
}
2 Likes

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