Why Ruby return inside a method doesn't stop code from running?

Why in the code below the following puts message still display on Ruby Console while the method located above it named exit_tool has a return?

module RafaelRivera
module Test
  class Main
    def activate
      puts "I expect this to appear in Ruby Console"
      exit_tool
      puts "Why this message appears in Ruby Console when method exit_tool has a return in it?"
    end

    def exit_tool
      Sketchup.send_action("selectSelectionTool:")
      return
    end

  end # class
end # module
end # module

model = Sketchup.active_model
model.select_tool RafaelRivera::Test::Main.new 

Because Sketchup.send_action merely queues up an action for execution at the next time SketchUp deems “safe”. I don’t know that SketchUp has ever fully defined what “safe” means, but certainly not until the current tool yields control back from the current Tool callback method (“activate” in this case).

Also, return inside a method just returns from that method, in this case passing control back to activate.

1 Like

Thank you slbaumgartner!

With the code below, I can make the exit_tool method work.

module RafaelRivera
module Test
  class Main
    def activate
      puts "I expect this to appear in Ruby Console"
      return if exit_tool
      puts "Not displayed in Ruby Console anymore, which is what I want "
    end
    
    def exit_tool
      UI.beep
      UI.messagebox("Thanks for using the tool")
      Sketchup.send_action("selectSelectionTool:")
      return true
    end

  end # class
end # module
end # module

model = Sketchup.active_model
model.select_tool RafaelRivera::Test::Main.new 

As Steve said Sketchup::send_action has “gotchas”.

So instead favor using direct API methods over send_actions, …
… in this case favor either:


However, a Ruby Tool class is an event driven object. It would be the user that instigates the deactivation of the tool. I cannot see any reason why you wish to exit the tool from it’s activate() method. The activate method needs to return in order for the user to do anything with the tool.

So I’d think that there would be a key press or menu item or toolbar button (or something) which would actually make the call to the exit_tool() method.

module RafaelRivera
module Test
  class Main

    def activate
      puts "#{Module.nesting[0].name} Tool Activated."
      reset()
    end

    def reset()
      @toolstate = 0
      # ... other initialization of state ...
    end

    def deactivate(view)
      UI.messagebox("Thanks for using the tool.\nTool Deactivated.")
      return true # This value will get returned to the SketchUp
      # engine, but *may* not get used for anything AFAIK.
    end

    def exit_tool
      UI.beep
      # Either:
      Sketchup.active_model.select_tool(nil)
      # Or:
      #Sketchup.active_model.tools.pop_tool
      # The following lines will get executed AFTER deactivate() ...
      puts "Exiting tool ..."
      return true
    end

    def onCancel(reason, view)
      if reason == 0 && (@toolstate.nil? || @toolstate == 0)
        exit_tool() 
      else # Reinitialize the tool to it's initial state
        reset()
        view.invalidate
      end
    end

    if !@loaded
      UI.menu("Plugins").add_item("RafaelRivera: Test Tool") {
        Sketchup.active_model.select_tool( RafaelRivera::Test::Main.new )
      }
      #
      @loaded = true
    end

  end # class
end # module
end # module

If the tool is being activated in a scenario where things are not ready, then an extension should instead create a UI::Command with a validation proc method to ensure that the tool cannot be activated (ie, graying | disabling of menu items and toolbar buttons.) in such scenarios.


Also is is bad coding form to create local variables in the toplevel ObjectSpace ...

Ie …

model = Sketchup.active_model
model.select_tool RafaelRivera::Test::Main.new 

… should be …

Sketchup.active_model.select_tool( RafaelRivera::Test::Main.new )

I know you’ll say it is just for a test, or that Ruby require has built-in cleanup of toplevel local variables when loading files … but (a) it’s better to train yourself to always follow good form and (b) if pasting this into the Ruby Console the built-in cleanup that in the require() method does not happen. (So, your global variable model propagates into all objects.)

so the test example above uses a test menu item so that everything can happen normally with the main SketchUp application window having the focus instead of the Ruby Console window.

:bulb:

3 Likes

… because activate just calls exit_tool… which in return returns nothing (or nil). Every function at some point returns, it would be weird if the callee would also stop the caller…

1 Like

A few words about return


The syntax for returning a value from a method varies from language to language. In Ruby, return can be omitted and the last evaluated value is returned.

return only affects the method enclosing it.


return is implicit with most programming languages. In code like the following, return is not needed:

def exit_tool
  Sketchup.send_action("selectSelectionTool:")
  return
end

If there is only one return ‘path’ in a method, returning a static value doesn’t infer any additional information. In the below method, true will always be returned.

def exit_tool
  Sketchup.send_action("selectSelectionTool:")
  return true
end

Most methods can be written such that return is never used. One might consider it to be a convenience statement. For instance, both the following are equivalent:

def meth1
  # code block a
  if x
    # code block b
  end
end

def meth2
  # code block a
  return unless x
  # code block b
end

Using return in lambdas or procs has specific rules.

1 Like

Thanks to everyone for clarifying my question. Below is another question …

If you want your method to return nothing, is it best practice to add return ‘nil’ to the code?

For example, the nil is added at the end of the method;

def example
  # code block
  nil
end

or

def example
  # code block
  return nil
end

If a method always returns a single, static value (like nil, true, false, etc), why is any other part of your code concerned about what it returns?

Or, if a method’s return value is always the same, why would another method calling it test the return value?

Note that an exception might be callback methods that may want a return value…

3 Likes

The method always returns something, you have no control over it. By default a method returns the value of the last statement. Best practice is to just ignore the return value in the caller if you don’t need a return value.

1 Like

As @MSP_Greg and @jim_foltz suggest, the answer depends on the “contract” that the method has with its caller. That is, does the caller expect a meaningful return value. Ruby always returns a reference to an object; there is no way to prevent this from happening. The only question is whether the caller expects to do anything with the value.

When the answer is yes, sometimes it is appropriate to return nil as a special value indicating that the method failed in some legitimate, anticipated way. Returning nil is cheaper than raising an exception, which can matter if the method is called from within a loop.

When a method is executed purely for side effects, not for the return value, it may be legitimate to return nil to prevent anyone from ever misunderstanding this aspect of the contract.

It is never appropriate for a method to always return the same, hard-wired value no matter what happened. That’s a waste of effort.

1 Like

Except … if it is an instance method of a class, and the method isn’t written in such a way that would return a meaningful value, … I in this case then consider returning the instance itself so that the method call can be used in call chaining. Ie …

obj.method.another_method.etc

This may also be appropriate for modules used primarily as mixin modules for classes.

However, setter instance methods using “=” suffix always by convention should return the argument as set or raise an appropriate exception.

But a normal module or class method doesn’t normally return itself. (I suppose it can, but it would be very rare.)

3 Likes

Good catch Dan, I overlooked the concept of call chaining.

2 Likes