Like most other programming languages that I know of, Rust provides a simple abstraction of common machine types (via
f[32|64]) and stops there. Ada takes it much further.
First of all, you are encouraged not to think in terms of what the machine can do, but in terms of what you want to do. This means that you can create integer types defined by their range of accessible values, floating-point types defined by their number of significant digits… and the compiler will pick the most suitable machine type for you given these constraints. In general, the language makes a large effort to significantly decouple the specification of arithmetic types (what you want from them) and their representation (how they’re mapped to hardware).
One “problem” is that in many cases, the compiler-selected machine type will do too much and allows incorrect values. Since the Ada designers are very serious about following specifications in a portable manner, there will be attempts to detect and report such events via compile-time and run-time checks, which is one thing to be mindful of when optimizing the performance of Ada code.
But let’s talk about a big positive consequence too. Bitfields and exotic integer types (such as those 10-bit ints from DSPs) are much more naturally expressible in Ada than in other languages, because you can much more naturally manipulate, say, 48-bit integers throughout your entire codebase, and not just fail at the point of inserting things in your bitfield where you realize that your 64-bit machine integer does not really fit in 48-bits. This flexibility in representing exotic integer types is, I believe, one reason why Ada remains popular in embedded developments to this day.
A natural consequence of this specification-based reasoning is also that everyone is going to want to define their own integer/float type, so Ada has first-class support for defining multiple incompatible integer and floating-point types, which can only interact with each other through explicit conversion. As you may imagine, this is super-convenient when you want to implement things like units of measurement (the good old
Meters types), but can also complicate interaction between libraries written by two different groups of people.
Ada also has first-class support for fixed-point arithmetic, which means using integers to represent fractional quantities (e.g. one possible mapping would be that 0.01 is represented as the machine integer 1, -4.2 is represented as the machine integer -420, and so on), and for all intents and purposes these look like floats in Ada code. I can only imagine that this is super-convenient when targeting those hardware architectures that do not have an FPU, although I never programmed those myself.
These are the main ones that I can remember off the top of my head.