Hard truth
Hard truth
Posted Sep 17, 2025 22:55 UTC (Wed) by NYKevin (subscriber, #129325)In reply to: Hard truth by danielthompson
Parent article: Comparing Rust to Carbon
- As a specifier on the function signature, it is part of the API, and signals that the function has nontrivial safety preconditions which are not enforced by the compiler. By convention, these preconditions are listed in the function's doc comment under the "Safety" header.
- As a block of code, it represents an acknowledgement that one or more operations inside of that block have safety preconditions which are not enforced, and a promise by the developer to uphold those preconditions manually. By convention, the block is commented with a SAFETY comment explaining why the applicable preconditions hold at that point in execution.
Because the compiler does not know what precondition the programmer is promising to uphold with any given unsafe block, nor the exact preconditions any given unsafe function or other unsafe operation* might require, it has no way of checking one against the other. It is therefore not unheard of for programmers to deliberately make all unsafe blocks as small as possible. If only one operation is wrapped in unsafe, then you only have to worry about the preconditions of that one operation, and will not accidentally make unrelated promises about constructs you wrongly believed were safe. Fortunately, Rust treats unsafe blocks (and blocks in general) as expressions, so you can pinpoint the unsafe operation you want to allow and leave the rest of the expression in safe code, if you so desire.
By allowing unsafe functions to do unsafe things in their bodies without a separate unsafe block, older Rust editions inappropriately conflated these two meanings. More importantly, they made it much harder to minimize the amount of code that falls within an unsafe block.
* To be pedantically correct, the compiler knows full well that a raw pointer needs to point at a live allocation of the correct type, if you want to dereference it, and the compiler has similar knowledge of most of the other unsafe superpowers. But the compiler does not know about all the state surrounding that raw pointer, so (for example) it does not know that the pointer is always valid for reads when some flag is set, or any more complicated variation of that pattern. You can write a wrapper which enforces such an invariant in safe code, but the implementation of that wrapper ultimately still needs to use unsafe internally. There is no workaround for this, because the whole point of unsafe is to function as an escape hatch for things the compiler does not understand.