The process of verifying and enforcing the constraints of types,
type checking, may occur at
compile time (a static check) or at
runtime (a dynamic check). If a language specification requires its typing rules strongly, more or less allowing only those automatic
type conversions that do not lose information, one can refer to the process as
strongly typed; if not, as
weakly typed. The terms are not usually used in a strict sense.
Static type checking Static type checking is the process of verifying the
type safety of a program based on analysis of a program's text (
source code). If a program passes a static type checker, then the program is guaranteed to satisfy some set of type safety properties for all possible inputs. Static type checking can be considered a limited form of
program verification (see
type safety), and in a type-safe language, can also be considered an optimization. If a compiler can prove that a program is well-typed, then it does not need to emit dynamic safety checks, allowing the resulting compiled binary to run faster and to be smaller. Static type checking for
Turing-complete languages is inherently conservative. That is, if a type system is both
sound (meaning that it rejects all incorrect programs) and
decidable (meaning that it is possible to write an algorithm that determines whether a program is well-typed), then it must be
incomplete (meaning there are correct programs, which are also rejected, even though they do not encounter runtime errors). For example, consider a program containing the code: : if then else Even if the expression always evaluates to true at runtime, most type checkers will reject the program as ill-typed, because it is difficult (if not impossible) for a static analyzer to determine that the else branch will not be taken. Consequently, a static type checker will quickly detect type errors in rarely used code paths. Without static type checking, even
code coverage tests with 100% coverage may be unable to find such type errors. The tests may fail to detect such type errors, because the combination of all places where values are created and all places where a certain value is used must be taken into account. A number of useful and common programming language features cannot be checked statically, such as
downcasting. Thus, many languages will have both static and dynamic type checking; the static type checker verifies what it can, and dynamic checks verify the rest. Many languages with static type checking provide a way to bypass the type checker. Some languages allow programmers to choose between static and dynamic type safety. For example, historically C# declares variables statically, Other languages allow writing code that is not type-safe; for example, in
C, programmers can freely cast a value between any two types that have the same size, effectively subverting the type concept.
Dynamic type checking and runtime type information Dynamic type checking is the process of verifying the type safety of a program at runtime. Implementations of dynamically type-checked languages generally associate each runtime object with a
type tag (i.e., a reference to a type) containing its type information. This runtime type information (RTTI) can also be used to implement
dynamic dispatch,
late binding,
downcasting,
reflective programming (reflection), and similar features. Most type-safe languages include some form of dynamic type checking, even if they also have a static type checker. The reason for this is that many useful features or properties are difficult or impossible to verify statically. For example, suppose that a program defines two types, A and B, where B is a subtype of A. If the program tries to convert a value of type A to type B, which is known as
downcasting, then the operation is legal only if the value being converted is actually a value of type B. Thus, a dynamic check is needed to verify that the operation is safe. This requirement is one of the criticisms of downcasting. By definition, dynamic type checking may cause a program to fail at runtime. In some programming languages, it is possible to anticipate and recover from these failures. In others, type-checking errors are considered fatal. Programming languages that include dynamic type checking but not static type checking are often called "dynamically typed programming languages".
Combining static and dynamic type checking Certain languages allow both static and dynamic typing. For example, Java and some other ostensibly statically typed languages support
downcasting types to their
subtypes, querying an object to discover its dynamic type and other type operations that depend on runtime type information. Another example is
C++ RTTI. More generally, most programming languages include mechanisms for dispatching over different 'kinds' of data, such as
disjoint unions,
runtime polymorphism, and
variant types. Even when not interacting with type annotations or type checking, such mechanisms are materially similar to dynamic typing implementations. Objects in object-oriented languages are usually accessed by a reference whose static target type (or manifest type) is equal to either the object's runtime type (its latent type) or a supertype thereof. This is conformant with the
Liskov substitution principle, which states that all operations performed on an instance of a given type can also be performed on an instance of a subtype. This concept is also known as subsumption or
subtype polymorphism. In some languages subtypes may also possess
covariant or contravariant return types and argument types respectively. Certain languages, for example
Clojure,
Common Lisp, or
Cython are dynamically type checked by default, but allow programs to opt into static type checking by providing optional annotations. One reason to use such hints would be to optimize the performance of critical sections of a program. This is formalized by
gradual typing. The programming environment
DrRacket, a pedagogic environment based on Lisp, and a precursor of the language
Racket is also soft-typed. Conversely, as of version 4.0, the C# language provides a way to indicate that a variable should not be statically type checked. A variable whose type is dynamic will not be subject to static type checking. Instead, the program relies on runtime type information to determine how the variable may be used.
Static and dynamic type checking in practice The choice between static and dynamic typing requires certain
trade-offs. Static typing can find type errors reliably at compile time, which increases the reliability of the delivered program. However, programmers disagree over how commonly type errors occur, resulting in further disagreements over the proportion of those bugs that are coded that would be caught by appropriately representing the designed types in code. Static typing advocates believe programs are more reliable when they have been well type-checked, whereas dynamic-typing advocates point to distributed code that has proven reliable and to small bug databases. The value of static typing increases as the strength of the type system is increased. Advocates of
dependent typing, implemented in languages such as
Dependent ML and
Epigram, have suggested that almost all bugs can be considered type errors, if the types used in a program are properly declared by the programmer or correctly inferred by the compiler. Static typing usually results in compiled code that executes faster. When the compiler knows the exact data types that are in use (which is necessary for static verification, either through declaration or inference) it can produce optimized machine code. Some dynamically typed languages such as
Common Lisp allow optional type declarations for optimization for this reason. By contrast, dynamic typing may allow compilers to run faster and
interpreters to dynamically load new code, because changes to source code in dynamically typed languages may result in less checking to perform and less code to revisit. This too may reduce the edit-compile-test-debug cycle. Statically typed languages that lack
type inference (such as
C and
Java prior to
version 10) require that programmers declare the types that a method or function must use. This can serve as added program documentation, that is active and dynamic, instead of static. This allows a compiler to prevent it from drifting out of synchrony, and from being ignored by programmers. However, a language can be statically typed without requiring type declarations (examples include
Haskell,
Scala,
OCaml,
F#,
Swift, and to a lesser extent
C# and
C++), so explicit type declaration is not required for static typing in all languages. Dynamic typing allows constructs that some (simple) static type checking would reject as illegal. For example,
eval functions, which execute arbitrary data as code, become possible. An
eval function is possible with static typing, but requires advanced uses of
algebraic data types. Further, dynamic typing better accommodates transitional code and prototyping, such as allowing a placeholder data structure (
mock object) to be transparently used in place of a full data structure (usually for the purposes of experimentation and testing). Dynamic typing typically allows
duck typing (which enables
easier code reuse). Many languages with static typing also feature
duck typing or other mechanisms like
generic programming that also enable easier code reuse. Dynamic typing typically makes
metaprogramming easier to use. For example,
C++ templates are typically more cumbersome to write than the equivalent
Ruby or
Python code since
C++ has stronger rules regarding type definitions (for both functions and variables). This forces a developer to write more
boilerplate code for a template than a Python developer would need to. More advanced runtime constructs such as
metaclasses and
introspection are often harder to use in statically typed languages. In some languages, such features may also be used e.g. to generate new types and behaviors on the fly, based on runtime data. Such advanced constructs are often provided by
dynamic programming languages; many of these are dynamically typed, although
dynamic typing need not be related to
dynamic programming languages.
Strong and weak type systems Languages are often colloquially referred to as
strongly typed or
weakly typed. In fact, there is no universally accepted definition of what these terms mean. In general, there are more precise terms to represent the differences between type systems that lead people to call them "strong" or "weak".
Type safety and memory safety A third way of categorizing the type system of a programming language is by the safety of typed operations and conversions. Computer scientists use the term
type-safe language to describe languages that do not allow operations or conversions that violate the rules of the type system. Computer scientists use the term
memory-safe language (or just
safe language) to describe languages that do not allow programs to access memory that has not been assigned for their use. For example, a memory-safe language will
check array bounds, or else statically guarantee (i.e., at compile time before execution) that array accesses out of the array boundaries will cause compile-time and perhaps runtime errors. Consider the following program of a language that is both type-safe and memory-safe: var x := 5; var y := "37"; var z := x + y; In this example, the variable will have the value 42. Although this may not be what the programmer anticipated, it is a well-defined result. If were a different string, one that could not be converted to a number (e.g., "
Hello, World"), the result would be well-defined also. A program can be type-safe or memory-safe and still crash on an invalid operation. This is for languages where the type system is not sufficiently advanced to precisely specify the validity of operations on all possible operands. But if a program encounters an operation that is not type-safe, terminating the program is often the only option. Now consider a similar example in C: int x = 5; char y[] = "37"; char* z = x + y; printf("%c\n", *z); In this example will point to a memory address five characters beyond , equivalent to three characters after the terminating zero character of the string pointed to by . This is memory that the program is not expected to access. In C terms this is simply
undefined behaviour and the program may do anything; with a simple compiler it might actually print whatever byte is stored after the string "37". As this example shows, C is not memory-safe. As arbitrary data was assumed to be a character, it is also not a type-safe language. In general, type-safety and memory-safety go hand in hand. For example, a language that supports pointer arithmetic and number-to-pointer conversions (like C) is neither memory-safe nor type-safe, because it allows arbitrary memory to be accessed as if it were valid memory of any type.
Variable levels of type checking Some languages allow different levels of checking to apply to different regions of code. Examples include: • The use strict directive in
JavaScript and
Perl applies stronger checking. • The declare(strict_types=1) in
PHP on a per-file basis allows only a variable of exact type of the type declaration will be accepted, or a TypeError will be thrown. • The Option Strict On in
VB.NET allows the compiler to require a conversion between objects. Additional tools such as
lint and
IBM Rational Purify can also be used to achieve a higher level of strictness.
Optional type systems It has been proposed, chiefly by
Gilad Bracha, that the choice of type system be made independent of choice of language; that a type system should be a module that can be
plugged into a language as needed. He believes this is advantageous, because what he calls mandatory type systems make languages less expressive and code more fragile. The requirement that the type system does not affect the semantics of the language is difficult to fulfill. Optional typing is related to, but distinct from,
gradual typing. While both typing disciplines can be used to perform static analysis of code (
static typing), optional type systems do not enforce type safety at runtime (
dynamic typing). == Polymorphism and types ==