CSC447

Concepts of Programming Languages

Closures

Instructor: James Riely

Closures

  • Runtime support for nested functions
    • particularly when lifetimes do not nest
  • Only applies to static / lexical scope

Top-Level Functions

  • Function declarations made at top level
  • Not hidden by scope

int loop (int n, int result) {
  if (n <= 1) {
    return result;
  } else {
    return loop (n - 1, n * result);
  }
}
int fact (int n) {
  return loop (n, 1);
}
          

Nested Functions - GCC

  • Nested functions allow for reuse of inner function name
  • Allowed by GCC, but not C standard

int fact (int n) {
  int loop (int n, int result) {
    if (n <= 1) {
      return result;
    } else {
      return loop (n - 1, n * result);
    }
  }
  return loop (n, 1);
}
          

$ gcc -c nested-fact.c 
$ gcc -pedantic -c nested-fact.c 
function.c: In function ‘fact’:
function.c:2:3: warning: ISO C forbids nested functions [-pedantic]
          

Nested Functions - GCC

  • Access variables from enclosing context
    • requires some runtime support

int fact (int n) {
  int loop (int i, int result) {
      if (i > n) {
      return result;
    } else {
      return loop (i+1, i * result);
    }
  }
  return loop (1, 1);
}
          

Why Nested Functions? Scala Example


def printElt [A,B] (f:A=>B) (x:A) : B = 
  println (x)
  f (x)

def mapDebug [A,B] (xs:List[A], f:A=>B) : List[B] = 
  xs.map (printElt (f))
          

def mapDebug [A,B] (xs:List[A], f:A=>B) : List[B] = 
  def printElt (x:A) : B = 
    println (x)
    f (x)  // use f from enclosing context
  xs.map (printElt)
          

def mapDebug [A,B] (xs:List[A], f:A=>B) : List[B] = 
  xs.map ((x:A) => { println (x); f (x) })  // anonymous function clearer
          

Nested Functions - Scope vs Lifetime

  • Limit scope of inner function
  • Lifetime of inner function vs lifetime of outer function?
  • Potentially unsafe, and requires more runtime support
    • more support than accessing variables from enclosing function

Nested Functions - GCC

If you try to call the nested function through its address after the containing function exits, all hell breaks loose. If you try to call it after a containing scope level exits, and if it refers to some of the variables that are no longer in scope, you may be lucky, but it's not wise to take the risk. If, however, the nested function does not refer to anything that has gone out of scope, you should be safe.

Nested Functions - GCC

  • Lifetime problems caused by nested functions

#include <stdio.h>
#include <stdlib.h>
typedef void (*funcptr) (int);
funcptr f (int x) {
  void g (int y) {
    printf ("x = %d, y = %d\n", x, y);
  }
  g (1);
  return &g;
}
int main (void) {
  funcptr h = f (10);
  (*h) (2);
  f (20);
  (*h) (3);
}

Nested Functions - GCC

  • Unsafe calls may or may not work

$ gcc -std=c99 nested-gcc.c
$ ./a.out
x = 10, y = 1 <- safe to call g, with x=10
x = 10, y = 2 <- unsafe to call h, created with x=10, GOOD!
x = 20, y = 1 <- safe to call g
x = 20, y = 3 <- unsafe to call h, created with x=10, BAD!
          

Nested Function - Clang

Nested Function - Clang


#include <stdio.h>
#include <stdlib.h>
#include <Block.h>
// ^funcptr for blocks; *funcptr for function pointers
typedef void (^funcptr) (int);
funcptr f (int x) {
  funcptr g;
  g = ^(int y) {
    printf ("x = %d, y = %d\n", x, y); // use x from enclosing defn
  };
  g = Block_copy (g);
  g (1); // OK, f's activation record still allocated
  return g;
}
int main (void) {
  funcptr h = f (10);
  h (2); // OK, because of Block_copy
  f (20);
  h (3); // OK, because of Block_copy
  Block_release	(h);
}
          

Nested Function - Clang

  • Blocks support is not turned on by default on Ubuntu

$ sudo apt-get install libblocksruntime-dev
          
  • Correct output

$ clang -fblocks nested-clang.c -lBlocksRuntime
$ ./a.out
x = 10, y = 1
x = 10, y = 2
x = 20, y = 1
x = 10, y = 3 <- safe to call h, created with x=10, GOOD!
          

Nested Function - Clang

  • Without Block_copy and Block_release

$ clang -fblocks nested-clang.c -lBlocksRuntime
$ ./a.out 
x = 10, y = 1
x = -1035955720, y = 2  <- unsafe to call h, created with x=10, BAD!
x = 20, y = 1
x = -1035955720, y = 3  <- unsafe to call h, created with x=10, BAD!
          

Nested Function - Swift

  • Nested functions work correctly in Swift

func f (_ x:Int) -> (Int) -> () {
  func g (_ y:Int) -> () { print ("x = " + String(x) + " y = " + String(y)) }
  g (1)
  return g
}
func main () {
  let h = f (10)
  h (2)
  let _ = f (20)
  h (3)
}
main()
          

$ swiftc nested-swift.swift 
$ ./nested-swift
x = 10 y = 1
x = 10 y = 2
x = 20 y = 1
x = 10 y = 3
          

Nested Function - Scala

  • Nested functions work correctly in Scala

def f (x:Int) : Int=>Unit =
  def g (y:Int) : Unit = println ("x = %d, y = %d".format (x, y))
  g (1)
  g

def main () = 
  val h = f (10)
  h (2)
  f (20)
  h (3)

main()
          

x = 10, y = 1
x = 10, y = 2
x = 20, y = 1
x = 10, y = 3 <- safe to call h, created with x=10, GOOD!
          

Nested Function - Java 8

  • Nested functions work correctly in Java 8

import java.util.function.IntConsumer;
public class NestedFunc1 {
  static IntConsumer f (int x) {
    IntConsumer g = y -> System.out.format ("x = %d, y = %d%n", x, y);
    g.accept (1);
    return g;
  }
  public static void main (String[] args) {
    IntConsumer h = f (10);
    h.accept (2);
    f (20); 
    h.accept (3);
  }
}
          

Nested Function - Java 8


$ javac NestedFunc1.java 
$ java NestedFunc1
x = 10, y = 1
x = 10, y = 2
x = 20, y = 1
x = 10, y = 3 <- safe to call h, created with x=10, GOOD!