Generics in Crystal

Crystal supports generics, which allow you to write flexible, reusable code that works with different types.

# As an example of a generic function, `slices_index` takes
# a slice of any comparable type and an element of that
# type and returns the index of the first occurrence of
# v in s, or -1 if not present.
def slices_index(s, v)
  s.index(v) || -1
end

# As an example of a generic type, `List` is a
# singly-linked list with values of any type.
class List(T)
  @head : Node(T)?
  @tail : Node(T)?

  private class Node(T)
    property next : Node(T)?
    property value : T

    def initialize(@value : T)
    end
  end

  # We can define methods on generic types just like we
  # do on regular types.
  def push(v : T)
    new_node = Node.new(v)
    if @tail.nil?
      @head = @tail = new_node
    else
      @tail.not_nil!.next = new_node
      @tail = new_node
    end
  end

  # all_elements returns all the List elements as an array.
  def all_elements
    elements = [] of T
    current = @head
    while current
      elements << current.value
      current = current.next
    end
    elements
  end
end

# Main function to demonstrate the usage
def main
  s = ["foo", "bar", "zoo"]

  # When invoking generic functions, Crystal can often infer
  # the types automatically.
  puts "index of zoo: #{slices_index(s, "zoo")}"

  lst = List(Int32).new
  lst.push(10)
  lst.push(13)
  lst.push(23)
  puts "list: #{lst.all_elements}"
end

main

To run the program, save it as generics.cr and use the crystal command:

$ crystal generics.cr
index of zoo: 2
list: [10, 13, 23]

In this Crystal version:

  1. We’ve implemented slices_index using Crystal’s built-in index method, which returns nil if the element is not found. We use the || operator to return -1 in that case.

  2. The List class is implemented as a generic type. In Crystal, we use (T) to declare a generic type parameter.

  3. Crystal uses ? to denote nullable types, so @head and @tail are declared as Node(T)?.

  4. We’ve implemented the push and all_elements methods similarly to the original example.

  5. In the main function, we demonstrate the usage of both the generic function and the generic type.

  6. Crystal can infer types in many cases, so we don’t need to explicitly specify types when calling slices_index or creating a List(Int32).

Crystal’s syntax for generics is similar to that of other languages, making it easy to write reusable code that works with different types.