This doesn’t match my understanding of unsafe. Callbacks are safe, even if they do a lot of unsafe things inside.
How much of the code inside the function is unsafe is uninteresting for the unsafety of a function.
unsafe in the function signature means that the caller should take care and that the function does not guarantee that Rusts guarantees hold even after the call to the function has finished. Take for example
std::mem::transmute: it happily returns you values of any type - and crash happily if you unsafely transmuted to the wrong type and later access it.
Another example of a function that is internally unsafe, but not externally is
slice::split_at_mut: it uses unsafety internally, but it guarantees that Rusts guarantees about the input reference and the two output references hold - it is safe to call.
Finally, unsafety doesn’t propagate outwards - just because it’s a piece of C code calling that function doesn’t make it unsafer: it’s the callers job to pass in the right values to call the function. For that reasons, callbacks do a lot of unsafe operations inside (mostly because they dereference a lot of raw pointers), but are by themselves not very unsafe: as long as the caller correctly calls the function, we’re all good. The fact that the implementor must take care with the input values comes from the raw input values not the chance that the calling code might call the function wrong.
P.S.: I read through the book and haven’t found where it’s necessarily ambiguous.
unsafe is for the points where you have to uphold invariants within Rust code. A callback passed to C can uphold all invariants if it were called from Rust code. The example given
std::mem::transmute can’t. Acquiring a raw pointer through any means is also strictly safe in Rust, as long as you don’t dereference it.