Some of the languages that come to mind without GC, Algol and its variants, NEWP, Mesa, Modula-2, Ada.
With GC, Lisp, Smalltalk, Modula-2+, Modula-3, Eiffel, Oberon, Oberon-2, Active Oberon, Component Pascal,....
Keeping the focus on the ones without GC, all of them were use to build full stack mainframes, so of course they supported dynamic structures. Just the memory was measured in KB not GB.
And, yes they did not prevent the programmer of doing a double free or calling free in memory that wasn't yet allocated.
But they prevented:
- implicit conversions between pointers and arrays
- using bad pointers for output parameters in function calls
- bad, implicit, conversions between numeric types
- implicit conversions between enumerated values and integers
- using non-existent enumerated values
- strings with missing terminator
- Using assignment operator when a comparison was intended
- Doing pointer manipulation all over the place instead of when it is really needed
For your m * n example, that is something you can easily do in Ada, even if a bit verbose.
Hence why Rust would be less needed, because we would be in a computing world, where the majority of the exploits C has brought into mainstream computing would be reduced to a very tiny subset, also with unsafe red signs that one has to explicitly opt-in.
And just like with Assembly we could probably manage it, if served in very tiny doses as infrastructure code for other languages.
As for tagged architectures, why not?
Specially since that is what Intel and other manufactures are now trying to push as a workaround tame C corruption errors.
But all of that is history now, and we really do need languages like Rust to make the lower layers of our OSes and language runtimes safe.