To continue on this vein, what I believe should be the right way to handle “unsafe” blocks is to put debug assertions directly inside the unsafe block that assert that the pre-conditions necessary to not have UB when invoking the unsafe operation/function (for example, if you are about to de-reference a pointer in an unsafe block, you should have a debug assertion that asserts that it isn’t null) and debug assertions after the invoked unsafe operation/function that verify any necessary post-conditions. This way, it is 100% clear that the programmer who used the “unsafe” at least thought about the pre/post-conditions necessary AND those conditions can be verified in debug mode. In release mode, the assertions go away and so have no overhead. If safety is absolutely critical, then use release assertions instead (and accept the small performance hit).
When deciding to use release assertions for additional safety, first try to formulate an explanation/proof of why it isn’t necessary, and document that in a comment along with the debug assertion. Only if you can’t construct such a satisfactory proof, then, use the release assertions.
That being said, if you can’t formulate such a proof/explanation, then you probably have other issues in your design/implementation that need addressed because, it is the caller’s responsibility to ensure that all pre-conditions to an unsafe operation/function are met prior to invocation. If your code, as the caller, isn’t clear about meeting those expectations, it’s a bug.
I think this is a reasonable way to approach the issue.