To demonstrate Structs and Records - lets assume we have a User class that can receive messages posted to it.

code/crystal/src/structs_n_records/user.cr
require "./message_record"

class User
  private getter name : String, email : String

  def initialize(@name, @email)
  end

  def to_s
    "#{name} <#{email}>"
  end

  def post_message(message : Message)
    puts message.to_s
  end
end

1. Records

Records are Structs with the initialize and getter automatically defined.

Records are actually Structs with Macros used to automatically generate the initialize and getter definitions - see: https://github.com/crystal-lang/crystal/blob/master/src/macros.cr

1.1. Records (no methods)

Thus the simplest Record definition for a Message would be:

record(Message, sender : User, text : String, receiver : User? = nil, topic : String? = nil)

# or without the parens

record Message, sender : User, text : String, receiver : User? = nil, topic : String? = nil

The following code demostrates how to use records.

code/crystal/src/structs_n_records/simple_record.cr
require "./user"

record(Message, sender : User, text : String, receiver : User? = nil, topic : String? = nil)

user_1 = User.new(name: "user_1", email: "[email protected]")
user_2 = User.new(name: "user_2", email: "[email protected]")

mesg_1 = Message.new(sender: user_2, text: "Hi")
mesg_2 = Message.new(sender: user_1, text: "Hoi", topic: "Greet")
mesg_3 = Message.new(sender: user_1, text: "Hoi", topic: "Greet", receiver: user_2)

user_1.post_message(mesg_1)
user_2.post_message(mesg_2)
user_2.post_message(mesg_3)

Run this using:

$ crystal src/structs_n_records/simple_record.cr

This of course has the same limitation as a Struct without a to_s methods.

1.2. Records (with methods)

Assuming you want a custom method then the record looks like:

code/crystal/src/structs_n_records/message_record.cr
require "./user"

record(Message, sender : User,          text : String,
                receiver : User? = nil, topic : String = "") do
  def to_s
    output = [] of String
    output << "-" * 30
    output << "From: #{sender.to_s}"
    output << "To: #{receiver.to_s}"  unless receiver.nil?
    output << "Topic: #{topic.to_s.strip}"
    output << text.strip
    output << "-" * 30
    output.join("\n")
  end
end

user_1 = User.new(name: "user_1", email: "[email protected]")
user_2 = User.new(name: "user_2", email: "[email protected]")

mesg_1 = Message.new(sender: user_2, text: "Hi")
mesg_2 = Message.new(sender: user_1, text: "Hoi", topic: "Greet")
mesg_3 = Message.new(sender: user_1, text: "Hoi", topic: "Greet", receiver: user_2)

user_1.post_message(mesg_1)
user_2.post_message(mesg_2)
user_2.post_message(mesg_3)

Run this using:

$ crystal src/structs_n_records/message_record.cr