>GC is not just for memory leaks, but memory safety in general.

No this is not true. Memory safety and memory leaks are different concepts. You can trigger a memory leak without violating memory safety. In fact a memory leak is not really an error recognized by an interpreter or a compiler or a GC. It is a logic error. A memory leak is only a leak because you interpret it as a leak. Otherwise the code is literally doing what you told it to do. It's similar to a logic error. I mean think about it, the interpreter can't know whether you purposefully allocated 1gb of memory or whether you accidentally allocated it.

Memory safety on the other hand is protection against violation of certain runtime protocols. The interpreter or runtime knows something went wrong and immediately crashes the program. It is a provable violation of rules and it is actually not open to interpretation like the memory leak was.

See python: https://docs.python.org/3/library/gc.html. You can literally disable the GC (during runtime) and the only other additional crash error that becomes more frequent is OOM. The GC literally just does reference counting and generational garbage collection... that's it.

I can tell you what makes python MORE memory safe then C++. It's just an additional runtime checks that are not zero cost.

x = [1,2]
print(x[2])

The above triggers an immediate exception that names the type of error (out of bounds) and the exact line that triggered it. This error will occur regardless of whether or not you disabled the GC. It happens because every index access to a list also checks against a stored length. If you're above that length it raises an exception. It's not zero cost but it's more safe.

For C++:

int x[] = {1,2};
std::cout<<x[2]<<std::endl;

This triggers nothing. It will run even though index 2 is beyond the bounds of the array. There is no runtime check because to do so would make the array data structure not zero cost. This is what happens during buffer overflows. It's one of the things that makes C++ a huge security problem.

Let's look at the type issue.

def head(input_list: List[int]) -> Optional[int]:
return input_list[0] if len(input_list) > 0 else None

x: int = head(2)

--------------------

#include <optional>
#include <vector>
std::Optional<int> head(const std::vector<int>& input_list){
return (input_list.length() > 0) ? input_list[0] : std::nullopt;
}

int main(){
auto x = head(2)
return 0;
}

Both pieces of code are identical. Python is type annotated for readability (not type checked). But both literally produce the same error messages (wrong input type on the call to head). Both will tell you there's a type error. It's just python happens at runtime and C++ happens at compile time. C++ has a slight edge in the fact that the error is caught as a static check. But this is only a SLIGHT advantage. Hopefully this example will allow you to see what I'm talking about as both examples literally have practically the exact same outcome of a type error. A minority of bugs are exclusively caught with type checking because runtime still catches a huge portion of the same bugs... and in general this is why overall C++ is still MUCH worse in terms of usability then python despite type checking.