rubysorbet

How do I reuse a T::Struct structure in another T::Struct


I have the following:

class Coordinate < T::Struct
  const :x, Integer
  const :y, Integer
end

class ThreeDCoordinate < T::Struct
  const :x, Integer
  const :y, Integer
  const :z, Integer
end

What I want is to have my ThreeDCoordinate inherit x and y of Coordinate so I don't have to rewrite them in ThreeDCoordinate. How can I accomplish this?


Solution

  • There is a way to do this, using T::InexactStruct, but you will have to give up being able to strongly type the initializer of your structs:

    # typed: true
    
    class Coordinate < T::InexactStruct
      const :x, Integer
      const :y, Integer
    end
    
    class ThreeDCoordinate < Coordinate
      const :z, Integer
    end
    
    
    coord = Coordinate.new(x: 2, y: 3)
    T.reveal_type(coord.x)
    T.reveal_type(coord.y)
    
    threeD = ThreeDCoordinate.new(x: 2, y: 3, z: 4)
    T.reveal_type(threeD.x)
    T.reveal_type(threeD.y)
    T.reveal_type(threeD.z)
    
    # Note that the constructors are not typed anymore:
    Coordinate.new(x: "foo", y: :bar) # This should fail but doesn't
    

    Sorbet Playground Link

    The problem with T::Struct and subclassing is that Sorbet creates an initializer for your struct that takes into account all of its declared fields. So for Coordinate the initializer has the signature params(x: Integer, y: Integer).void but for ThreeDCoordinate it has the signature params(x: Integer, y: Integer, z: Integer).void. Now these signatures are not compatible with each other, so Sorbet does not allow you to subclass one from the other.

    T::InexactStruct allows you to give up the strong typing in the constructor and trade it in for being able to do inheritance on typed structs.