Struct Embedding in Crystal

Crystal supports embedding of structs to express a more seamless composition of types. This is similar to inheritance but with some key differences.

class Base
  property num : Int32

  def initialize(@num : Int32)
  end

  def describe
    "base with num=#{@num}"
  end
end

# A `Container` *includes* a `Base`. This is similar to embedding in other languages.
class Container
  include Base
  property str : String

  def initialize(@num : Int32, @str : String)
    super(@num)
  end
end

# Main program
co = Container.new(1, "some name")

# We can access the base's fields directly on `co`.
puts "co={num: #{co.num}, str: #{co.str}}"

# We can also access the base's methods directly on `co`.
puts "describe: #{co.describe}"

# Crystal doesn't have interfaces, but we can use abstract classes or modules
# to achieve similar behavior.
abstract class Describer
  abstract def describe
end

# `Container` now implements the `Describer` abstract class
# because it includes `Base` which has the `describe` method.
d = co as Describer
puts "describer: #{d.describe}"

To run the program:

$ crystal run struct_embedding.cr
co={num: 1, str: some name}
describe: base with num=1
describer: base with num=1

In Crystal, we use the include keyword to mix in modules or classes, which is similar to embedding in other languages. The property macro is used to create getter and setter methods for instance variables.

Crystal doesn’t have interfaces, but we can use abstract classes or modules to achieve similar behavior. In this example, we’ve used an abstract class Describer to demonstrate a concept similar to interfaces.

The syntax for creating and using structs (or classes in this case) is quite similar to the original, with some Crystal-specific adjustments. The as keyword is used for type casting, which is necessary when assigning a Container instance to a Describer variable.

Crystal’s type system is more strict than some other languages, so type annotations are often necessary. However, in many cases, Crystal can infer types, making the code cleaner and more concise.