Rust has things that have dynamic runtime check overhead that are often used. Reference counters, array size, e.t.c You have to have runtime checks because of Rice's theorem.
The only way around this would be to have a absolutely strict type system that defines the finite sets of data that memory can hold.
But for compile time checks, its not hard. For example, the I would do it C is that every pointer gets an optional permission id through some syntax when created. Any expression involving modifying that pointer needs to have the appropriate permission id stated, any dereference operation needs to have the appropriate permission stated, and free is only limited to the function where the pointer was created with malloc. Then any pointer created in assignment from an expression involving that pointer inherits the permission id.
So you simply have a system of tracing where memory gets used.
But all of this is overkill tbh, when you can just use existing static memory analyzers that pretty much do the same thing. And coupled with dynamic memory analyzers like valgrind with appropriate testing, you don't even need to do runtime checks within your code.