The Crystal Programming Language Forum

Problems with rescue block

Hey guys

I’ve been study crystal and I’ve try to bring some ruby gems, that I like, in crystal.
So I tried to implement something like ruby micro-case(GitHub - serradura/u-case: Represent use cases in a simple and powerful way while writing modular, expressive and sequentially logical code.),
but I got a problem that I can’t solve that.

Basically, I have a Result class that has a value attribute and others.

class Result(U)
  getter is_success : Bool
  getter value : U
  getter result_type : String
  property is_proccessed : Bool
  
  def initialize(@is_success : Bool, @value : U, @result_type = "ok") 
    @is_proccessed = false
  end
  
  def on_error(&block : Result(U) -> H) : Result(U) forall H
    if !is_success && !is_proccessed
    	block.call(self) 
        self.is_proccessed = true
    end
    self
  end
  
  def on_success(&block : Result(U) -> H) : Result(U) forall H
    if is_success && !is_proccessed
    	block.call(self) 
        self.is_proccessed = true
    end
    self
  end
end

Result class has some chainable methods like on_error and on_success,
the propouse is to execute block when succesfully or a error block when fail.

To use that class I have created a DivisionCase class that’s make a simple math operation and returns a Hash with result

class DivisionCase
    
	def self.call(one : Int32, two : Int32)
		result = (one / two).to_f
		success_data = {"division" => result}        
		Result(typeof(success_data)).new(true, success_data, "success")
	rescue 
		data = {"error" => {"attr" => "attr error message here"}}
		Result(typeof(data)).new(false, data, "error_type")
	end
end

Putting everything together, we have something like this:

 result = DivisionCase.call(1, 0)
    .on_error() do |res|
      pp "simple error"
	end
    .on_success() do |res|
      p "Division #{res.value["division"]} "
	end

And this doesn’t work, when I execute the code above I have the following error message:

error in line 13
Error: no overload matches 'Proc(Result(Hash(String, Float64)), String)#call' with type Result(Hash(String, Hash(String, String)))

Overloads are:
 - Proc(*T, R)#call(*args : *T)

I tried to investigate what’s the problem and I figure out that error occurs when I put the rescue code, if I remove the rescue block, all the things is working.

I really don’t know how to do that, if someone can help, please tell me

Here is the code working (without a rescue block) Carcin
And here is the code that isn’t work Carcin

I think the gist of the issue is that by introducing the rescue block you’re introducing another possible execution path that is causing issues with the typing of your logic. For example, this might help understand what’s going on:

typeof(DivisionCase.call(1, 0)) # => (Result(Hash(String, Float64)) | Result(Hash(String, Hash(String, String))))

Because the type of .call can return a union of Result instances that each have diff data, this breaks your fluent callback logic. Since if it returns success the on_error block couldn’t be called with the data from the success execution flow. Similarly, if the error flow happened, the on_success callback would fail in the same way.

Possible solutions involve making use of exceptions to handle error states and have a single block that assumes success. Another option could be maybe try having dedicated Result types versus combining them into one. Hopefully that’s enough to get you back on track.

EDIT: A third option could be to introduce a dedicated object that could be used to contain the data, this way the result type doesn’t need to be aware of that.

1 Like

Ohhh thank you for your advice, after I read it I understand where I’m wrong.
And I have written that code:

class DivisionCase   
	def self.call(one : Int32, two : Int32)
	  begin
		result = (one / two).to_i
		data = {"division" => result}      
		result_type = "success"
		is_success = true
	  rescue 
		data = {"error" => {"attr" => "attr error message here"}}
		result_type = "error"
		is_success = false
	  end
	  Result(typeof(data)).new(is_success, data, result_type)
	end
end

Thus the compiler accepted my code

Thank you

1 Like