How do you make subtype fields consistent and mandatory?

Consider the following code:

abstract type AbstractTransport end

mutable struct Car <: AbstractTransport

    name::String
    engine_type::String
    number_of_people::Int32

    function Car(name::String, engine_type::String, number_of_people::Int32)
        # Validation left out. 
        new(name,engine_type,number_of_people)
    end 
end
mutable struct StarShip <: AbstractTransport

    number_of_people::Int32

    function StarShip(number_of_people::Int32)
        # Validation left out. 
        new(number_of_people)
    end 
end

As you can see both transports are sub types of AbstractTransport but they don’t have consistent fields. I won’t get an error until I use a method that requires certain fields:

function transport_info(transport::AbstractTransport)
    println(transport.name)
    println(transport.engine_type)
end
uss_enterprise = StarShip(100)
transport_info(uss_enterprise) # type StarShip has no field name

Is there a way to make certain fields mandatory during construction of sub types? For example in OOP, you would have an Abstract class that contains all the attributes common to all subclasses. When constructing an object, you would pass the required fields to the Abstract class using the constructor.

In OOP, I would put the name and engine_type in the abstract class, so even if a subclass doesn’t provide new attributes it at least has to provide name and engine_type.

I could use hasfields() but then each method that uses an AbstractTransport would need to check for the required fields.

Julia has no data inheritance. IMHO, the Julian way is to either:

  1. Do not ever access fields of an object that you received from a parameter typed generically. Create two functions name and engine_type, and document they should be defined for every subtype of this abstract type, use these functions instead. In other, basically, use setters and getters you want to use OOP concepts.
  2. Document the abstract type to indicate any subtype needs to have a field called engine_type. I think this is clunky and has basically no performance improvements over getters and setters, but hey, it is a solution.
  3. Combine both previous solutions with a small dose of enforcement. When you define the getters and setters, let the generic version (that does not define the type of any arguments) check if the field exists, if it exists use the field, otherwise throw an error explaining to the used that “Type X is a subtype of Type Y and breached the contract by not having a field called z.”. For the best you should yet use the getters and setters in your generic code but at least people that subtype your type “in the wrong fashion” will get a good error message and they do not need to define their own getters and setters (the generic one will do the job for them).

Probably there some hacky improvements to these ideas, but I would recommend following either path 1 or 3. A solution in a kind different direction would be creating a TransportInfo concrete type, with both name and engine_type fields, and require (by means of documentation) that your AbstractTransport subtypes define a method for get_transport_info (or some better name) that always return a TransportInfo object.