Once you do get to explicit memory management, Ada does a number of things. I'm going to a list here just for my own sanity.
First, all deallocations are essentially marked unsafe. You use Unchecked_Deallocation() to free memory.
More importantly, it provides memory pools, and subpools. Each pointer type can be associated with a pool (or subpool). Once the pool goes out of scope, all memory is freed. You can use this to avoid explicit deallocations yourself. Pools also control allocations and deallocations, and there exist Debug pools that can help ensure memory is accessed correctly.
Ada also requires that stack-based objects be declared as "aliased" before you may make a pointer to them, so it's always clear where there might be trouble.
Finally (I think), Ada has a concept of accessibility levels. Essentially a pointer cannot point to an object that is more deeply scoped than itself. This isn't the perfection of the Rust borrow checker, but it does quite a bit.
As for C FFI, Ada does that quite well. It's got a package in the standard library with C interface types, and aspects for marking things for C FFI.