Usage of "empty" structs


#1

Often in packages and Julia source code, I see structs that have no field. For example,

abstract type RangeStepStyle end
struct RangeStepRegular <: RangeStepStyle end # range with regular step
struct RangeStepIrregular <: RangeStepStyle end # range with rounding error

What’s the point of creating a type without any fields. How do you use these and how does this relate to type stability of the code?


#2

It’s often used for multiple dispatch. Example:

abstract type RangeStepStyle end

struct RangeStepRegular <: RangeStepStyle end
struct RangeStepIrregular <: RangeStepStyle end

range_step(n, ::Type{RangeStepRegular}) = println("regular step: $n")
range_step(n, ::Type{RangeStepIrregular}) = println("not a normal step: $n")

range_step(3, RangeStepRegular)
range_step(6, RangeStepIrregular)

Result:

regular step: 3
not a normal step: 6

#3

Also, by making it a subtype of RangeStepStyle, you can enforce the argument in non-specialized methods, e.g.:

struct InvalidStep end

step_forward(n, style::Type{<:RangeStepStyle}) = range_step(n, style)

step_forward(3, RangeStepRegular)
step_forward(6, RangeStepIrregular)
step_forward(4, InvalidStep)

Result:

regular step: 3
not a normal step: 6
ERROR: LoadError: MethodError: no method matching step_forward(::Int64, ::Type{InvalidStep})

#4

You can also use type parameters like f(::NoFields{N}) where N = N == 0 will specialize the function on the type parameter. Because N is a type parameter, the value of N == 0 can be evaluated ahead of time during compilation before the function is actually called, thus eliminating the calculation at run time.


#5

This is discussed in the manual btw:

https://docs.julialang.org/en/v1/manual/methods/#Trait-based-dispatch-1