They are consistent.
In your first example, it is fine for the field itself to be safe.
The struct does not contain unsafe code with safety requirements whose violation could lead to UB. Even if val were zero, this would only be a logical error, not undefined behavior.
If you decide that the struct does hold a type invariant (e.g., val != 0 must always hold),
then that invariant should be stated explicitly, and the filed, as well as other struct APIs may break the invariant should be declared unsafe.
In general, type invariants should be defined first, and unsafe should follow from them.
In your second example, crazy should be declared unsafe unless the struct declare
the type invariant self.val != 0. It can only be safe with the type invariant.
Note that even with the type invariant, the type may also provide unchecked constructors; such constructors must be declared unsafe.
In this way, any undefined behavior related to the internal unsafe callee can only be triggered through explicit unsafe constructor (or unsafe field related operations).
This is analogous to Vec, where the len field and the method set_len are unsafe
because Vec has type invariants related to len. As long as these invariants hold, other memory access APIs of the struct can remain safe. Vec also provides unsafe constructors and methods which may break these invariants if misused.
In the third example, the name NonZero alone does not establish a type invariant.
Type names are not type invariants. If such a struct is able to create a zero value via safe code, then the implementation is buggy. If the type claims a type invariant but also exposes ways to violate it (e.g., unchecked constructors), then those constructors must be declared unsafe,
and callers must explicitly uphold the invariant.