.. _readme-lifetime-checking:

===========================
Checking Variable Lifetimes
===========================

As of Chapel 1.18, Chapel includes a compiler component called the
lifetime checker. The lifetime checker produces errors at compile time to
reveal potential memory errors. Note that the Chapel lifetime checker is
not complete - that is, there are programs with memory errors that it
will not detect. However, we hope that it offers a good balance between
being easy to work with and catching common memory errors at
compile-time. See also :ref:`readme-nil-checking` which discusses a
related component that discovers nil dereferences at compile time.

Defining Scope and Lifetime
===========================

Scope
+++++

Variables in Chapel have a lexical scope within which it is legal to
access the variable. For example:

.. code-block:: chapel

  module DemonstrateScopes {
    proc function() {
      var f: int;
      // scope of `f` includes the body of this function
      {
        // and any nested blocks (including loops, conditionals, etc).

        var x: int;
        // `x` scope ends here, but `f` scope does not
      }
      // it would be an error to access `x` here
    }
    // it would be an error to access `f` here

    {
      var b: int;
      // `b`s scope extends to the end of this block
    }
    // it would be an error to try to use `b` here

    var g: int;
    // `g` is a global variable and its scope extends
    // to any code using this module.
  }

A scope can be contained in another scope. For example, the scope of ``x``
is contained within the scope of ``f`` in the above example. In other
words, anywhere that ``x`` can be accessed, ``f`` can also be accessed.
In such a case we say that the scope of ``x`` is smaller than the scope of
``f``.

Lifetime
++++++++

The lifetime of a variable indicates when that variable can be safely
used.

 * Variables that cannot refer to another value, such as numeric
   variables, have a lifetime equal to their lexical scope.
 * Variables that can refer to other variables include ``borrowed`` class
   instances and ``ref`` variables. These variables get their lifetime
   from the lifetime of the variable that they refer to.
 * ``owned`` and ``shared`` variables have lifetime equal to their scope.

Note that ``owned`` and ``shared`` variables can be returned or assigned
without impacting their lifetime. The lifetime checker just checks that a
``borrow`` from such a variable does not outlive the variable itself.

.. code-block:: chapel

  module DemonstrateLifetimes {
    proc function() {
      var i: int;
      // `i`s lifetime extends to the end of this block
      ref r = i;
      // `r` refers to `i`, so it's lifetime == `i`s lifetime
      var own = new owned SomeClass();
      // `own`s lifetime extends to the end of this block,
      // (at which point the class instance may be deleted)
      var borrow = own.borrow();
      // `borrow`s lifetime extends to the end of this block
      // because its lifetime matches `own`
    }

    var global: owned SomeClass;
    proc settingGlobal() {
      var x = new owned SomeClass();
      // lifetime of x extends for entire function body

      global = x; // transfers the instance from x to global
      // leaving x storing `nil`

      // lifetime of `x` extends to here, but an attempt
      // to use `x` would result in an error from
      // compile-time nil checking.
    }
  }

Similarly to scopes, lifetimes may be contained within each other.
Ultimately, a lifetime is just the scope of some variable, and so we can
say that one lifetime is smaller or larger than another, just as we can
say that a scope is smaller or larger than another scope.

Example Errors
==============

The lifetime checker is designed to catch errors such as:

 * returning a reference to or borrow from a function-local variable
 * assigning a value with a shorter lifetime to something with a larger scope

When the lifetime for a variable is smaller than its scope, that usually
means that there is some point in the program where accessing that
variable could lead to a memory error. There are some cases where the
analysis indicates a memory error could occur, but a human programmer
might know that it cannot for other reasons.

Returning a Reference to a Local Variable
+++++++++++++++++++++++++++++++++++++++++

.. code-block:: chapel

  // returnsref.chpl
  proc refTo(ref x) ref {
    return x;
  }

  proc returnsRefLocal() ref // note `ref` return intent
  {
    var i: int;
    return refTo(i); // returns `i` by reference
                     // but `i` goes out of scope here
  }
  ref r = returnsRefLocal();
  var val = r; // accesses invalid memory

::

  returnsref.chpl:6: In function 'returnsRefLocal':
  returnsref.chpl:9: error: Reference to scoped variable cannot be returned
  returnsref.chpl:8: note: consider scope of i


Returning a Borrow From a Local Owned Instance
++++++++++++++++++++++++++++++++++++++++++++++

.. code-block:: chapel

  // returnsborrow.chpl
  class SomeClass { var field: int; }
  proc borrowLocal() {
    var obj = new owned SomeClass;
    return obj.borrow(); // returns borrow of `obj`
    // but `obj` goes out of scope (and `delete`s the instance) here
  }

  var b = borrowLocal();
  var y = b.field; // accesses deleted memory

::

  returnsborrow.chpl:3: In function 'borrowLocal':
  returnsborrow.chpl:5: error: Scoped variable cannot be returned
  returnsborrow.chpl:4: note: consider scope of obj

Assigning a Borrow to something with Longer Scope
+++++++++++++++++++++++++++++++++++++++++++++++++

.. code-block:: chapel

  // assignsborrow.chpl
  class SomeClass { }

  {
    var bor: borrowed SomeClass;
    {
      var obj = new owned SomeClass();
      bor = obj.borrow(); // borrow of `obj` escapes
      // but `obj` goes out of scope (and `delete`s the instance) here
    }
    writeln(bor); // uses freed memory
  }

::

  assignsborrow.chpl:8: error: Scoped variable bor would outlive the value it is set to
  assignsborrow.chpl:7: note: consider scope of obj


Lifetime Inference
==================

The lifetime checker starts by inferring the lifetime of each variable.
It considers the ways that the variable is set:

 * if the variable is a reference to another variable, then
   its lifetime will be the scope of that variable
 * if a borrow is assigned or initialized from another variable, then
   its lifetime will be at most the lifetime of the other variable
 * if the variable is set by a function call, then the lifetime is inferred
   according to rules described below

Inference proceeds until the minimum inferred lifetime of each variable is
established.

Inferred Lifetimes of Arguments
+++++++++++++++++++++++++++++++

For methods, the ``this`` argument is assumed to have longer lifetime than the
actual arguments and only the ``this`` argument is assumed to have a lifetime
that can be returned.

For non-methods, all formals are considered to have a lifetime that can be
returned.

Inferred Lifetime of Function Call Results
++++++++++++++++++++++++++++++++++++++++++

For ``x = f(a, b, c)``, the lifetime of ``x`` is inferred to be the
minimum lifetime of the arguments ``a``, ``b``, ``c`` that have lifetimes
that could be returned.

For a method call, such as ``y = receiver.f(a, b, c)``, the lifetime will
be inferred to be the lifetime of ``receiver``.

If these inferred lifetimes are not appropriate for a function, the lifetimes
can be specified with a lifetime annotation.

Lifetime Annotations
====================

Certain functions need to override the default lifetime inference rules.
This can be accomplished by placing a ``lifetime`` clause after the
return type. These ``lifetime`` clauses share some similarities with
``where`` clauses. For example:

.. code-block:: chapel

  class C { var x: int; }
  var globalOwned = new owned C(1);
  var globalBorrow = globalOwned.borrow();

  // Default lifetime inference assumes that the
  // returned lifetime is the lifetime of arg,
  // but that's not appropriate here.
  //
  // The lifetime annotation indicates that the returned value
  // has the lifetime of globalBorrow.
  proc returnsGlobalBorrow(arg: borrowed C)
    lifetime return globalBorrow
  {
    return globalBorrow;
  }

Other functions need to assert a relationship between the lifetimes of
their arguments. This pattern comes up with functions that append some
data to a data structure.

.. code-block:: chapel

  record Collection {
    type elementType;
    var element: elementType;
  }

  // Without lifetime annotation, the compiler will raise an error,
  // because `this` is assumed to have larger lifetime than `arg`,
  // and so the assignment will set something with a longer lifetime
  // to something with a shorter lifetime.
  //
  // The lifetime clause `lifetime this < arg` avoids that error
  // by informing the compiler that `this` (and by extension, `this.element`)
  // need to have lifetime no longer than `arg`.
  proc Collection.addElement(arg: elementType)
    lifetime this < arg
  {
    this.element = arg;
  }

Note that the lifetime clause needs to be written in terms of formal
arguments, including ``this`` for methods, and possible outer variables.
In particular, in the above, the constraint is between ``this`` and
``arg`` rather than ``this.element`` and ``arg``. ``this.element`` will
have its lifetime inferred to be the lifetime of ``this``, so these are
equivalent.

In some cases, it is more natural to write the lifetime annotation in
terms of what assignments the function may make. For example:

.. code-block:: chapel

  proc myswap(ref lhs: borrowed MyClass, ref rhs: borrowed MyClass)
    lifetime lhs=rhs, rhs=lhs
  {
    var tmp = lhs;
    lhs = rhs;
    rhs = tmp;
  }

Here the lifetime checker ensures that the lifetimes of the actual
arguments are suitable for performing the assignments between formals
that are indicated in the lifetime clause ``lifetime lhs=rhs, rhs=lhs``.
