Generic class restricted to a certain type

I am writing a FRACTRAN interpreter in Crystal and, to speed things up, am going to store numbers as lists of prime factors instead of BigInts. I want to be able to scale the integer size (e.g. keep numbers as UInt64s until one overflows, then switch to BigInts). Using Crystal’s generic classes works quite well. For example:

class Factorization(T)
  def initialize(array_size : Int = 0)
    @array = Array(T).new array_size
  end
end

But, what if I wanted to restrict T to only classes under Int? This does not work:

class Factorization(T : Int)
  def initialize(array_size : Int = 0)
    @array = Array(T).new array_size
  end
end

Should something like this be written instead?

class Factorization(T : Int)
  class TypeError < Exception; end

  def initialize(array_size : Int = 0)
    raise TypeError.new unless T.is_a? Int

    @array = Array(T).new array_size
  end
end

But this feels very much like a kludge and only triggers at runtime. Is there a better solution?

Can use macros to get it to be a compile time error:

class Factorization(T)
  class TypeError < Exception; end
 
  def initialize(array_size : Int = 0)
    {% T.raise "Factorization only supports Int subclasses" unless T <= Int %}
 
    @array = Array(T).new array_size
  end
end
 
Factorization(String).new 1

Related: Generic Specializations · Issue #3298 · crystal-lang/crystal · GitHub

1 Like