Static typing in a programming language can be helpful for enforcing certain guarantees at compile time- but are types the only tool for this job? Are there other ways of specifying invariants?

For example, a language or environment could help enforce a guarantee regarding array length, or regarding the relationships between the inputs to a function. I just haven't heard of anything like this outside of a type system.

A related thing I was wondering about is if there are any non-declarative ways to do static analysis (types are declarative, for the most part).

3 Answers 3


Static type systems are a kind of static analysis, but there are many static analyses that aren’t generally encoded in type systems. For example:

  • Model checking is an analysis and verification technique for concurrent systems that allows you to prove that your program is well-behaved under all possible thread interleavings.

  • Data flow analysis gathers information about the possible values of variables, which can determine whether some computation is redundant, or some error is not accounted for.

  • Abstract interpretation conservatively models the effects of a program, usually in such a way that the analysis is guaranteed to terminate—type checkers may be implemented in a similar manner to abstract interpreters.

  • Separation logic is a program logic (used for example in the Infer analyzer) which can be used to reason about program states, and identify issues such as null pointer dereferences, invalid states, and resource leaks.

  • Contract-based programming is a means of specifying preconditions, postconditions, side effects, and invariants. Ada has native support for contracts and can verify some of them statically.

Optimising compilers do many small analyses in order to build intermediate data structures for use during optimisation—such as SSA, estimates of inlining costs, instruction pairing information, and so on.

Another example of non-declarative static analysis is found in the Hack typechecker, where normal control-flow constructs can refine the type of a variable:

$x = get_value();
if ($x !== null) {
    $x->method();    // Typechecks because $x is known to be non-null.
} else {
    $x->method();    // Does not typecheck.

And speaking of “refining”, back in the land of type systems, refinement types (as used in LiquidHaskell) pair types with predicates that are guaranteed to hold for instances of the “refined” type. And dependent types take this further, allowing types to depend on values. The “hello world” of dependent typing is usually the array concatenation function:

(++) : (a : Type) -> (m n : Nat) -> Vec a m -> Vec a n -> Vec a (m + n)

Here, ++ takes two operands of type Vec a m and Vec a n, being vectors with element type a and lengths m and n respectively, which are natural numbers (Nat). It returns a vector with the same element type whose length is m + n. And this function proves this constraint abstractly, without knowing the specific values of m and n, so the lengths of the vectors may be dynamic.

  • What's a type system? I realize that I don't actually know. The definition on Wikipedia is circular: en.wikipedia.org/wiki/Type_system
    – Max Heiber
    Feb 15, 2016 at 0:40
  • 2
    @mheiber: A static type system is simply a static analysis that ascribes types (e.g., int, int -> int, forall a. a -> a) to terms (e.g., 0, (+ 1), \x -> x). Other analyses can ascribe different properties unrelated to data type, for example, side effects (pure, io), visibility (public, private), or state (open, closed). In practice, many of these properties can be checked in the same implementation as the type checking/inference, so the distinction isn’t totally clear-cut.
    – Jon Purdy
    Feb 15, 2016 at 20:51

@JonPurdy's answer is better, but I'd like to add a few more examples:


  • syntax checking

  • linting


  • Rust enables the programmer to specify whether "bindings" are mutable, and enforces these constraints.

  • This is sort-of related: some languages enable some code to be run at compile time, which means that many things that would otherwise be runtime errors can be caught at compile time. Some examples are macros and Nim language procedures marked with the compileTime pragma.

  • Logic programming is basically building up a program by providing assertions.

Semi-static typing:

  • Facebook's Flow enables a hybrid between dynamic and static typing. The idea is that even dynamic code is implicitly typed. Flow lets you run a server that watches your code as it runs to detect possible type errors, even if you don't type-annotate your functions.

Type analysis does not mean a lot.

Agda is known to have a Turing-complete type system, very different (and much more difficult to compute) than ML languages (e.g. Ocaml).

  • Agda, goes to a great effort to not have a "Turing-complete type system" and even not have a "Turing-complete term system".
    – user833970
    Mar 18, 2019 at 15:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.