At the abstract level, you can include anything you want in a language you're designing.
At the implementation level, it's inevitable that some of those things will simpler to implement, some will be complicated, some can be made fast, some are bound to be slower, and so on. To account for this, designers often have to make hard decisions and compromises.
At the implementation level, one of the fastest ways we have come up for accessing a variable is finding out its address and loading the contents of that address. There are specific instructions in most CPUs for loading data from addresses and those instructions usually need to know how many bytes they need to load (one, two, four, eight, etc) and where to put the data they load (single register, register pair, extended register, other memory, etc). By knowing the size of a variable, the compiler can know exactly which instruction to emit for usages of that variable. By not knowing the size of a variable, the compiler would need to resort to something more complicated and probably slower.
At the abstract level, the point of subtyping is to be able to use instances of one type where an equal or more general type is expected. In other words, code can be written that expects an object of a particular type or anything more derived, without knowing ahead of time what exactly this would be. And clearly, as more derived types can add more data members, a derived type does not necessarily have the same memory requirements as its base types.
At the implementation level, there's no simple way for a variable of a predetermined size to hold an instance of unknown size and be accessed in a way you'd normally call efficient. But there is a way to move things around a little and use a variable not to store the object, but to identify the object and let that object be stored somewhere else. That way is a reference (e.g. a memory address) -- an extra level of indirection that ensures that a variable only needs to hold some kind of fixed-size information, as long as we can find the object through that information. To achieve that, we just need to load the address (fixed-size) and then we can work as usual using those offsets of the object that we know are valid, even if that object has more data at offsets we don't know. We can do that because we don't concern ourselves with its storage requirements when accessing it anymore.
At the abstract level, this method allows you to store a (reference to a) string
into an object
variable without losing the information that makes it a string
. It's fine for all types to work like this and you might also say it's elegant in many respects.
Still, at the implementation level, the extra level of indirection involves more instructions and on most architectures it makes each access to the object somewhat slower. You can allow the compiler to squeeze more performance out of a program if you include in your language some commonly used types that don't have that extra level of indirection (the reference). But by removing that level of indirection, the compiler cannot allow you to subtype in a memory safe way anymore. That's because if you add more data members to your type and you assign to a more general type, any extra data members that don't fit in the space allocated for the target variable will be sliced away.