Home > OS >  How do I reuse a T::Struct structure in another T::Struct
How do I reuse a T::Struct structure in another T::Struct

Time:03-24

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?

CodePudding user response:

As far as I know, Sorbet does not support exactly the feature that you asked.

However, I am using interfaces to achieve similar typing goals:

module Coordinate
  extend T::Helpers
  extend T::Sig

  interface!

  sig { abstract.returns(Integer) }
  def x; end

  sig { abstract.returns(Integer) }
  def y; end
end

module ThreeCoordinate
  extend T::Helpers
  extend T::Sig

  include Coordinate

  interface!

  sig { abstract.returns(Integer) }
  def z; end
end

class ThreeDLocation < T::Struct
  extend T::Sig
  
  include ThreeCoordinate
  
  # Will be complained by Sorbet because of missing interface
  const :x, Integer
end

class ThreeDCenter < T::Struct
  extend T::Sig

  include ThreeCoordinate

  sig { override.returns(Integer) }
  def x
    0
  end

  sig { override.returns(Integer) }
  def y
    0
  end

  sig { override.returns(Integer) }
  def z
    0
  end
end

Try out on Sorbet Playground to see how Sorbet static checks help ensure the typing.

Although there might be some repetitions in the codes, we have achieved the goal that classes implementing ThreeCoordinate is guaranteed to have the interface of Coordinate as well.

Also, using this approach, the implementation is more flexible. We can also implement something like ThreeDCenter above without being tied down to using Struct properties only.

CodePudding user response:

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.

  • Related