groovy

Groovy method cannot access variable in enclosing scope


I know that this is what closures are for, but shouldn't the below work as well?:

def f = 'foo'
def foo() {
   println(f)
}
foo()

It results in:

Caught: groovy.lang.MissingPropertyException: No such property: f for class: bar
groovy.lang.MissingPropertyException: No such property: f for class: bar
   at bar.foo(bar.groovy:4)
   at bar.run(bar.groovy:7)

Solution

  • In a groovy script (as opposed to class), your code is essentially equivalent to:

    class ScriptName {
      def main(args) {
        new ScriptName().run(args)
      }
    
      def run(args) {
        def f = 'foo'
        foo()
      }
    
      def foo() {
        println(f)
      }
    }
    

    the 'implicit' enclosing class created by groovy for groovy scripts is always present but not visible in your code. The above makes it obvious why the foo method does not see the f variable.

    You have a couple of options

    option 1 - binding

    See the groovy docs on script bindings for details.

    // put the variable in the script binding
    f = 'foo'
    

    (note that we do this without the def keyword)

    this is shorthand for:

    binding.setVariable('f', 'foo')
    

    where the script binding is visible everywhere for groovy scripts and this makes the variable essentially 'global'.

    option 2 - @Field annotation

    See groovy docs on the Field annotation for details.

    import groovy.transform.Field
    ...    
    // use the groovy.transform.Field annotation 
    @Field f = 'foo' 
    

    the Field annotation is specifically there to give groovy scripts the ability to add fields to the 'implicit' enclosing class. The generated class would then look something like the following:

    class ScriptName {
      def f = 'foo'
    
      def main(args) {
        new ScriptName().run(args)
      }
    
      def run(args) {
        foo()
      }
    
      def foo() {
        println(f)
      }
    }
    

    which also essentially makes the variable accessible 'globally' in the script.