Right–you’re thinking of how things work in a language like C++, where there’s a difference between the compile-time type (e.g. AbstractTransport
) and the run-time type (e.g. Intrepid
). Julia doesn’t have this distinction at all (see this comment: Compile-time vs run-time actions - #3 by StefanKarpinski). No matter how you store an Intrepid
or what generic container you use to hold it, Julia will always remember that it was an Intrepid
and will always let you treat it as such.
As an extreme example, consider a Vector{Any}
like []
. In a language with different compile-time and run-time types, you wouldn’t be able to do anything with the contents of such an array, since the Any
abstract type doesn’t provide any methods. This clearly isn’t the case in Julia, where you can store anything you want in a Vector{Any}
and still access each individual element in its proper type.
As for how this is actually stored, it’s probably best to focus on what the language actually ensures, which is: No matter what the value type of your Dict
is, an Intrepid
will always be an Intrepid
. In practice, if you have a dictionary with a non-concrete value type, then there will be some kind of metadata stored giving the actual type of each value. If you have a Dict{String, Intrepid}
, then there’s no need for that metadata for each individual value, which is one reason why concrete container types are more efficient. I’m sure other more knowledgeable people can give you details on the exact form of that metadata (my mental model is “a pointer and a type tag”), but that’s probably only necessary to understand if you actually want to modify the internals of Julia itself.