Which is better for traits? Subtyping vs parametric typing

I want to define traits (I don’t know so much about traits but perhaps traits). I know 2 ways.

Abstract type and subtypes:

abstract type YAMLVersion end

struct YAMLV1_1 <: YAMLVersion end

struct YAMLV1_2 <: YAMLVersion end

forwardchars!(::YAMLV1_1, stream::TokenStream, n::Integer=1) #...

The way of Julia Base’s RoundingMode

struct YAMLVersion{T} end

const YAMLV1_1 = YAMLVersion{:Version1_1}()

const YAMLV1_2 = YAMLVersion{:Version1_2}()

forwardchars!(::YAMLVersion{:Version1_1}, stream::TokenStream, n::Integer=1) #...

which is better?

The first version is definitely more common, and what I would use most of the time. I’m really not sure why RoundingMode uses the struct method, I would guess it’s to solve a specific and niche problem.

I’d be curious why RoundingMode is implemented the way it is! I don’t see the reason.

A downside of this approach is decreased type safety, because constructing YAMLVersion subtypes with arbitrary parameters is possible.

Here’s a third way:

struct YAMLV1_1 end
struct YAMLV1_2 end
const YAMLVersion = Union{YAMLV1_1,YAMLV1_2}

Its distinguishing feature is that it doesn’t allow adding more subtypes of YAMLVersion.