# Acceptable pattern when two structs of the same type share the same fields

**URL:** <https://discourse.julialang.org/t/acceptable-pattern-when-two-structs-of-the-same-type-share-the-same-fields/57959>\
**Category:** General Usage\
**Created:** [March 25, 2021, 6:17pm UTC](https://discourse.julialang.org/t/acceptable-pattern-when-two-structs-of-the-same-type-share-the-same-fields/57959 "2021-03-25T18:17:26Z")\
**Posts on this page:** 15\
**Page:** 1

<div class="post-metadata">

**Author:** ![anon60034542](https://avatars.discourse-cdn.com/v4/letter/a/90ced4/32.png) [@anon60034542](https://discourse.julialang.org/u/anon60034542)\
**Post date:** [March 25, 2021, 6:17pm UTC](https://discourse.julialang.org/t/acceptable-pattern-when-two-structs-of-the-same-type-share-the-same-fields/57959/1 "2021-03-25T18:17:26Z")

</div>

Consider the following `struct`s:

```julia
abstract type AbstractTransport end

struct Warp
    limit::Float32
end

struct Galaxy <:AbstractTransport
    name::String
    drive::Warp
end

struct Intrepid <:AbstractTransport
    name::String
    drive::Warp
end

```

Both `Galaxy` and `Intrepid` share the same field names of `name::String` and `drive::Warp`. On the surface it isn’t too bad, but consider if the number of fields grow and the introduction of an inner constructor for validation, that’s a lot of code that would be repeated over again.

Is there an acceptable pattern to handle this? The only work around I can think of is to separate the common fields into another `struct`, `ShipDetails`:

```julia
struct ShipDetails
    name::String
    drive::Warp

    function ShipDetails(name::String, drive::Warp)
     # validation
     new(name,drive)
    end 
end

```

I can restructure the `struct`s like this:

```julia

struct Galaxy <:AbstractTransport
    attributes::ShipDetails
end

struct Intrepid <:AbstractTransport
    attributes::ShipDetails
end

```

---

<div class="post-metadata">

**Author:** ![marius311](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/marius311/32/3953_2.png) [@marius311](https://discourse.julialang.org/u/marius311)\
**Post date:** [March 25, 2021, 6:27pm UTC](https://discourse.julialang.org/t/acceptable-pattern-when-two-structs-of-the-same-type-share-the-same-fields/57959/2 "2021-03-25T18:27:07Z")

</div>

I basically made [GitHub - marius311/CompositeStructs.jl](https://github.com/marius311/CompositeStructs.jl) for this exact issue. It’s definitely not a super established solution (the package isn’t even registered), but it has worked well for me thus far and I’m curious to keep developing it. From the readme:

> Splices the fields of one struct into another. E.g.:

```julia
struct Foo{X,Y}
    x :: X
    y :: Y
end

@composite struct Bar{X,Y,Z}
    Foo{X,Y}...
    z :: Z
end

# equivalent to defining:
struct Bar{X,Y,Z}
    x :: X
    y :: Y
    z :: Z
end

```

There’s a couple other similar packages out there mentioned in the readme, although this has the unique feature that you don’t need to do anything special to the “parent” struct.

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [March 25, 2021, 6:56pm UTC](https://discourse.julialang.org/t/acceptable-pattern-when-two-structs-of-the-same-type-share-the-same-fields/57959/3 "2021-03-25T18:56:46Z")

</div>

I did this below once. I do not think that is very pretty, but in particular if there is any clear reason NOT do it, I will be glad to know.

```julia
julia> macro MyFields()
         esc(quote
           x::Float64
           y::Float64
         end)
       end
@MyFields (macro with 1 method)

julia> struct A
         @MyFields()
       end

julia> struct B
         @MyFields()
         z::Float64
       end

julia> fieldnames(A)
(:x, :y)

julia> fieldnames(B)
(:x, :y, :z)

```

---

<div class="post-metadata">

**Author:** ![rdeits](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rdeits/32/286_2.png) [@rdeits](https://discourse.julialang.org/u/rdeits)\
**Post date:** [March 25, 2021, 7:03pm UTC](https://discourse.julialang.org/t/acceptable-pattern-when-two-structs-of-the-same-type-share-the-same-fields/57959/4 "2021-03-25T19:03:40Z")

</div>

> [@anon60034542](#):
>
> The only work around I can think of is to separate the common fields into another `struct` , `ShipDetails` :

I think this is a good approach. It doesn’t require any tools, and it makes the set of shared fields explicit. You can also use `@forward` from Lazy.jl to make it easier to access the fields inside `attributes` without having to do `ship.attributes.drive`.

The composition approach that you’ve described also makes it easy to see how you might create multiple different sets of shared attributes (after all, there are probably some fields in common to a warp-capable ship and other fields in common for a combat ship) without having to worry about multiple inheritance and the complexities that it creates in other languages.

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [March 25, 2021, 7:13pm UTC](https://discourse.julialang.org/t/acceptable-pattern-when-two-structs-of-the-same-type-share-the-same-fields/57959/5 "2021-03-25T19:13:31Z")

</div>

Using functions to retrieve the properties is quite clear and practical as well:

```julia
julia> ship = Galaxy(ShipDetails("ship",Warp(1.0)))
Galaxy(ShipDetails("ship", Warp(1.0f0)))

julia> name(x::AbstractTransport) = x.attributes.name
name (generic function with 1 method)

julia> name(ship)
"ship"

```

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [March 26, 2021, 1:11pm UTC](https://discourse.julialang.org/t/acceptable-pattern-when-two-structs-of-the-same-type-share-the-same-fields/57959/6 "2021-03-26T13:11:02Z")

</div>

I would also go with composition, or possibly a field (see below, preferable) or a type parameter:

```julia
using ArgCheck

struct Galaxy end

struct Intrepid end

struct Ship{T}
    kind::T
    name::String
    drive::Warp
    function Ship(kind::T, name, drive) where T
        @argcheck warp <= 10 "you must be desperate for a plot device"
        new{T}(kind, name, drive)
    end
end

Ship(Galaxy(), "Enterprise-D", 9.6)

```

You can also make kinds a subtype of some abstract type, etc.

---

<div class="post-metadata">

**Author:** ![sostock](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sostock/32/5546_2.png) [@sostock](https://discourse.julialang.org/u/sostock)\
**Post date:** [March 26, 2021, 1:45pm UTC](https://discourse.julialang.org/t/acceptable-pattern-when-two-structs-of-the-same-type-share-the-same-fields/57959/7 "2021-03-26T13:45:31Z")

</div>

If all `AbstractTransport` subtypes have the same fields, you could also turn `AbstractTransport` into a concrete type and use a type parameter to distinguish different kinds:

```julia
struct Transport{Kind}
    name::String
    drive::Warp
end

const Galaxy = Transport{:Galaxy}
const Intrepid = Transport{:Intrepid}

```

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [March 26, 2021, 4:09pm UTC](https://discourse.julialang.org/t/acceptable-pattern-when-two-structs-of-the-same-type-share-the-same-fields/57959/8 "2021-03-26T16:09:48Z")

</div>

I also mentioned that as an option above, but IMO having an actual field is much more convenient. And of course costless, for singleton types.

When in doubt, a good rule of thumb is to work with values, not types.

---

<div class="post-metadata">

**Author:** ![sostock](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sostock/32/5546_2.png) [@sostock](https://discourse.julialang.org/u/sostock)\
**Post date:** [March 26, 2021, 5:12pm UTC](https://discourse.julialang.org/t/acceptable-pattern-when-two-structs-of-the-same-type-share-the-same-fields/57959/9 "2021-03-26T17:12:06Z")

</div>

I agree that a field is more convenient, since you can then decide at any time that `Galaxy` shouldn’t be a singleton type anymore.

---

<div class="post-metadata">

**Author:** ![kmundnic](https://avatars.discourse-cdn.com/v4/letter/k/e19adc/32.png) [@kmundnic](https://discourse.julialang.org/u/kmundnic)\
**Post date:** [March 26, 2021, 6:09pm UTC](https://discourse.julialang.org/t/acceptable-pattern-when-two-structs-of-the-same-type-share-the-same-fields/57959/10 "2021-03-26T18:09:26Z")

</div>

An approach I’ve used is the following:

```julia
abstract type AbstractTransport end

transports = [:Galaxy, :Intrepid]

struct Warp
    limit::Float32
end

for transport in transports
    @eval struct $transport <: AbstractTransport
        name::String
        drive::Warp
    end
end

```

What I like about it is that if you come up with another ship name, you can just add it to `transports`. You can use the structs the same way as in your definition:

```julia
julia> Galaxy("ship", Warp(10))
Galaxy("ship", Warp(10.0f0))

```

---

<div class="post-metadata">

**Author:** ![anon60034542](https://avatars.discourse-cdn.com/v4/letter/a/90ced4/32.png) [@anon60034542](https://discourse.julialang.org/u/anon60034542)\
**Post date:** [March 26, 2021, 9:05pm UTC](https://discourse.julialang.org/t/acceptable-pattern-when-two-structs-of-the-same-type-share-the-same-fields/57959/11 "2021-03-26T21:05:18Z")

</div>

I’ll admit that having 2 separate `struct`s simply because a ship is a different class is overkill. Perhaps a better idea would be `StarShip <:AbstractTransport` and `CruiseShip <: AbstractTransport`. Both differ in function to be different `struct`s.

---

<div class="post-metadata">

**Author:** ![JeffreySarnoff](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jeffreysarnoff/32/1980_2.png) [@JeffreySarnoff](https://discourse.julialang.org/u/JeffreySarnoff)\
**Post date:** [March 27, 2021, 3:26pm UTC](https://discourse.julialang.org/t/acceptable-pattern-when-two-structs-of-the-same-type-share-the-same-fields/57959/12 "2021-03-27T15:26:37Z")

</div>

Your solution is very appropriate.

Just as an example of another approach,  
here is a very shallow abstract type chain.  
_(not really appropriate in this small example, still good to know)_

* * *

```julia
abstract type AbstractTransport end
abstract type StellarTransport <: AbstractTransport end
abstract type PlanetaryTransport <: AbstractTransport end

struct Warp
    limit::Float32
end

struct StarShip <: StellarTransport
   name::String
   drive::Warp
end

struct CruiseShip <: PlanetaryTransport
  name::String
  drive::Warp
end

galaxy = StarShip("Galaxy", Warp(10))
cruiser = CruiseShip("Cruiser", Warp(2))

function maxwarp(ship::StellarTransport)
    return Warp(ship.drive.limit * 7/8)
end

function maxwarp(ship::PlanetaryTransport)
    return Warp(ship.drive.limit * 5/8)
end

```

---

<div class="post-metadata">

**Author:** ![anon60034542](https://avatars.discourse-cdn.com/v4/letter/a/90ced4/32.png) [@anon60034542](https://discourse.julialang.org/u/anon60034542)\
**Post date:** [March 27, 2021, 3:43pm UTC](https://discourse.julialang.org/t/acceptable-pattern-when-two-structs-of-the-same-type-share-the-same-fields/57959/13 "2021-03-27T15:43:40Z")

</div>

I was just thinking about this not too long ago! Very nice.

In this case, I would make `Warp` into `Engine` or better, make it `abstract`. Inside `CruiseShip`, I would make an `Engine[]`, they might have a primary and backup engine, and in `StellarTransport`, I would do the same, because you often hear warp or impulse engines.

---

<div class="post-metadata">

**Author:** ![boutor2](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/boutor2/32/27869_2.png) [@boutor2](https://discourse.julialang.org/u/boutor2)\
**Post date:** [July 12, 2022, 11:35am UTC](https://discourse.julialang.org/t/acceptable-pattern-when-two-structs-of-the-same-type-share-the-same-fields/57959/14 "2022-07-12T11:35:12Z")

</div>

Can you expand a bit on the use of `@forward`? I wanted to use this solution but accessing the attributes like

```julia
intrepid.name

```

instead of

```julia
intrepid.attributes.name

```

but it does not look like `@forward` can do that, right?

---

<div class="post-metadata">

**Author:** ![AUK1939](https://avatars.discourse-cdn.com/v4/letter/a/c57346/32.png) [@AUK1939](https://discourse.julialang.org/u/AUK1939)\
**Post date:** [December 6, 2023, 3:57am UTC](https://discourse.julialang.org/t/acceptable-pattern-when-two-structs-of-the-same-type-share-the-same-fields/57959/15 "2023-12-06T03:57:51Z")

</div>

one way to do this would be you would create accessor functions like this

```julia
name(sd::ShipDetails) = sd.name 
drive(sd::ShipDetails) = sd.drive

```

then forward these calls to Galaxy.attributes and Intrepid.attributes like so

```julia
using Lazy
@forward Galaxy.attributes name, drive
@forward Intrepid.attributes name, drive

```

so now you can write

```julia
name(my_galaxy)
name(my_intrepid)

```

I like @lmiq’s macro approach too
