When designing and implenting an object-oriented programming language, at some point one must make a choice about implementing fundamental types (like int, float, double or equivalents) as classes or something else. Clearly, languages in the C family have a tendency not to define them as classes (Java has special primitive types, C# implements them as immutable structs, etc).I can think of a very important advantage when fundamental types are implemented as classes (in a type system with a unified hierarchy): these types can be proper Liskov subtypes of the root type. Thus, we avoid complicating the language with boxing/unboxing (either explicit or implicit), wrapper types, special variance rules, special behavior, etc.Of course, I can partially understand why language designers decide the way they do: class instances tend to have some spatial overhead (because the instances may contain a vtable or other metadata in their memory layout), that primitives/structs don't need to have (if the language doesnt allow inheritance on those).Is spatial efficiency (and improved spatial locality, especially in large arrays) the only reason why fundamental types are often not classes?I've generally assumed the answer to be yes, but compilers have escape analysis algorithms and thus they can deduce whether they can (selectively) omit the spatial overhead when an instance (any instance, not just a fundamental type) is proven to be strictly local.Is the above wrong, or is there something else I'm missing?
What are the caveats of implementing fundamental types (like int) as classes?
object oriented;language design;compiler;type systems
Yes, it pretty much comes down to efficiency. But you seem to be underestimating the impact (or overestimating how well various optimizations work).First, it's not just spatial overhead. Making primitives boxed/heap-allocated has performance costs too. There's the additional pressure on the GC to allocate and collect those objects. This goes doubly if the primitive objects are immutable, as they should be. Then there's more cache misses (both because of the indirection and because less data fits into a given amount of cache). Plus the bare fact that load the address of an object, then load the actual value from that address takes more instructions than load the value directly.Second, escape analysis isn't go-faster fairy dust. It only applies to values that, well, don't escape. It's certainly nice to optimize local calculations (such as loop counters and intermediate results of calculations) and it will give measurable benefits. But a much larger majority of values live in the fields of objects and arrays. Granted, those can be subject to escape analysis themselves, but as they're usually mutable reference types, any aliasing of them presents a significant challenge to the escape analysis, which now has to prove that those aliases (1) don't escape either, and (2) don't make a difference for the purpose of eliminating allocations.Given that calling any method (including getters) or passing an object as argument to any other method can help the object escape, you'll need interprocedural analysis in all but the most trivial cases. This is far more expensive and complicated.And then there are cases where things really do escape and can't reasonably be optimized away. Quite many of them, actually, if you consider how often C programmers go through the trouble of heap-allocating things. When an object containing an int escapes, escape analysis ceases to apply to the int as well. Say goodbye to efficient primitive fields.This ties into another point: The analyses and optimizations required are seriously complicated and an active area of research. It's debatable whether any language implementation ever achieved the degree of optimization you suggest, and even if so, it's been a rare and herculean effort. Surely standing on the shoulders of these giants is easier than being a giant yourself, but it's still far from trivial. Don't expect competitive performance any time in the first few years, if ever.That is not to say such languages can't be viable. Clearly they are. Just don't assume it will be line-for-line as fast as languages with dedicated primitives. In other words, don't delude yourself with visions of a sufficiently smart compiler.