3

I'm just writting an Exception, which should stores a Class object as a field for the error message process.

class BadType < Exception
    getter should_be : Class
    getter actual : Class
end
def feed(pet : Animal, food : Food)
    raise BadType.new should_be: Cat, actual: pet.class if food == "fish" && !pet.is_a?(Cat)
end

But, Class is abstract so cannot be used here as a field type.

So, how to solve this in my case? I hadn't found any derived class of Class, so does it mean one can never store a Class object as a field? But here my problem is a meaningful usage for this (any type check depends on input may require this BadType).

I'm not sure whether I missed something, so I came here first.

XQY
  • 552
  • 4
  • 17

2 Answers2

4

Class can't (yet) be used as a ivar type. Maybe it never will, I don't know.

But you can actually use generics for concrete data types, inferred from the arguments to the constructor:

# possible workaround
class Foo(T, U)
  def initialize(@bar : T, @baz : U)
  end
end

Foo.new String, Int32

I don't know your exact use case, but chances are you don't really need these values as classes. You probably can't do much with it anyway and drawing from your example I guess it's mostly for showing debugging information.

So it is very likely that just storing the names of the classes (as strings) would be a better solution for this problem.

# better solution
class Foo
  @bar : String
  @baz : String
  def initialize(bar : Class, baz : Class)
    @bar = bar.name
    @baz = baz.name
  end
end

Foo.new String, Int3

The generic arguments mean a new concrete type is created for every combination of classes used with Foo. That can have an impact on compiler performance.

I would most definitely use strings for this. Even if you need the classes for some particularly processing later on, it's probably better to just map the strings to the constants using a macro-generated lookup table.

Johannes Müller
  • 5,581
  • 1
  • 11
  • 25
3

Try generics:

class BadType(ClassA, ClassB) < Exception
  getter should_be : ClassA
  getter actual : ClassB

  def initialize(@should_be, @actual)
    @message = "Bad type: should be #{@should_be}, actual is #{@actual}"
  end
end

def feed(pet : Animal, food : Food)
  raise BadType(Animal.class, Animal.class).new should_be: Cat, actual: pet.class if food == "fish" && !pet.is_a?(Cat)
end

class Animal
end

record Food, food : String do
  def ==(other_food)
    @food == other_food
  end
end

class Cat < Animal
end

class Dog < Animal
end

feed pet: Dog.new, food: Food.new("fish")

Output:

Unhandled exception: Bad type: should be Cat, actual is Dog (BadType(Animal:Class, Animal:Class))
  from /eval:11:3 in 'feed'
  from /eval:29:1 in '__crystal_main'
  from /usr/lib/crystal/crystal/main.cr:104:5 in 'main_user_code'
  from /usr/lib/crystal/crystal/main.cr:93:7 in 'main'
  from /usr/lib/crystal/crystal/main.cr:133:3 in 'main'
  from __libc_start_main
  from _start
  from ???

Demo: https://carc.in/#/r/4pgs

Faustino Aguilar
  • 823
  • 6
  • 15