Fact-checked by Grok 2 weeks ago

Static dispatch

Static dispatch is a core mechanism in languages for resolving calls at , based on the declared (static) type of the or object, rather than its type. This contrasts with , where selection depends on the actual object type determined during execution, enabling runtime polymorphism. In languages like C++, static dispatch applies to non-virtual methods and , allowing the to bind calls directly to the appropriate implementation for efficiency. Java employs static dispatch for static methods and final methods, which cannot be overridden, ensuring predictable behavior without overhead. The primary advantage of static dispatch lies in its performance benefits, as it avoids the need for virtual method tables or late , resulting in faster execution and smaller code size compared to dynamic alternatives. However, it limits flexibility, as it does not support the polymorphic behavior essential for inheritance hierarchies where subclasses may override superclass methods.

Core Concepts

Definition

Static dispatch refers to the mechanism in programming languages where the resolves the specific implementation of a or to be called based on the static types of the arguments and receiver, which are known at , thereby avoiding runtime polymorphism. This process, also known as early binding, ensures that method calls are fixed during , contrasting with that defers resolution to . Key characteristics of static dispatch include its reliance on static typing to determine call targets ahead of execution, the absence of tables (v-tables) since no runtime type inspection is required, and the resulting direct linkage to implementations. These features promote predictable behavior and enable optimizations like inlining, as the exact code path is finalized before the program runs. The concept emerged in early compiled languages like C, developed in the early 1970s for system programming on Unix. It gained formal structure in object-oriented paradigms during the 1980s, particularly through C++'s introduction of non-virtual methods alongside virtual ones for polymorphism. A simple illustration of static dispatch in pseudocode demonstrates compile-time resolution for a known type:
type Rectangle = { width: number, height: number };

function draw(shape: Rectangle) {
    // Compiler binds this to Rectangle-specific implementation
    print("Drawing rectangle");
}

let rect: Rectangle = { width: 10, height: 5 };
draw(rect);  // Resolved at compile time to the above function body

Resolution Mechanism

Static dispatch resolves method or function calls at compile time through a series of compiler-driven steps that ensure type-specific implementations are selected and integrated into the final code. This process begins with the parsing of source code declarations and usages, where the compiler builds an (AST) to represent the program's structure, including function signatures and call sites. During semantic analysis, the performs to determine the static types of all expressions and variables, relying on context clues such as explicit annotations or usage patterns to resolve ambiguities without involvement. This step is crucial as static dispatch presupposes a statically typed where all relevant types are known prior to . Once types are established, overload resolution occurs for ambiguous calls: the evaluates candidate functions by matching types against lists, ranking viable options based on exact matches, promotions, and conversions, and selecting the best fit to eliminate ambiguity. In or templated contexts, the applies monomorphization to handle polymorphism statically. Here, definitions are into specialized, monomorphic versions for each unique set of concrete type arguments used in the program; for example, a operating on integers and strings would yield two distinct implementations, each optimized for its type. This typically follows a two-phase lookup process: first, validating the template's syntax independently of arguments, then substituting types during actual use to resolve dependent names and generate concrete code. The linker may subsequently resolve references to these instantiated symbols across compilation units, ensuring complete without altering the dispatch decisions. The concludes resolution by emitting low-level code where calls are replaced with direct jumps or inline expansions to the chosen implementations, avoiding indirect addressing and enabling further optimizations like . This flow can be represented textually as follows:
  1. and AST construction: Analyze source to identify declarations, calls, and type hints.
  2. and checking: Infer and verify static types for all elements.
  3. Resolution phase:
  4. : Produce direct calls and link symbols.
  5. Linking: Resolve external references to complete the .
This mechanism ensures zero runtime overhead for dispatch, as all decisions are finalized before execution.

Comparison with Dynamic Dispatch

Fundamental Differences

Static dispatch resolves method calls at compile time using the static types of the arguments, ensuring that the exact implementation is determined before execution begins. In contrast, dynamic dispatch defers this resolution to runtime, relying on the actual dynamic types of objects to select the appropriate method. This fundamental distinction yields greater predictability in static dispatch, as the compiler fixes the binding early and eliminates ambiguity, whereas dynamic dispatch offers flexibility by accommodating runtime variations in object types. A key contrast lies in the number of possible implementations: static dispatch typically selects a single, type-specific implementation per call site through mechanisms like monomorphization, avoiding decision-making. Dynamic dispatch, however, can choose among multiple implementations based on conditions, enabling behavior that adapts to the actual object hierarchy. Regarding polymorphism, static dispatch supports ad-hoc polymorphism via overloading, where the selects implementations based on argument types, and through generic code generation. It does not, however, enable subtype polymorphism, which depends on dynamic resolution of inheritance-based overrides. Static dispatch detects type mismatches and invalid calls at , catching errors before program execution and promoting safer code. Dynamic dispatch, by deferring resolution, risks runtime exceptions if the actual object lacks the expected , potentially leading to failures only observable during execution. The following table summarizes core contrasts:
AspectStatic DispatchDynamic Dispatch
Resolution ScopeCompile-time, based on static typesRuntime, based on dynamic types
Binding TypeEarly binding to specific implementationsLate binding via runtime selection
Polymorphism SupportAd-hoc (overloading) and parametricSubtype (inheritance hierarchies)

Resolution Timing

Static dispatch resolves all method calls and polymorphic behaviors entirely during the compilation process, encompassing phases from to , with no decisions left for execution. This early ensures that the specific implementations are selected based on the known types at , avoiding any ambiguity or deferral that could complicate . As a result, the compiler can generate direct machine code references without embedding mechanisms for later resolution. In contrast to , which relies on virtual tables (vtables) for selection, static dispatch eliminates the need for such structures, preventing any lookup overhead during execution and allowing calls to be directly inlineable by the . This absence of dispatch tables means that function invocations translate to straightforward jumps in the generated code, facilitating optimizations like inlining that reduce call overhead to zero. The impact on program flow is profound, as static dispatch establishes predictable execution paths fully known at build time, enabling the to optimize the entire without uncertainty from type variability. This supports advanced analyses, such as precise branch prediction and , across the program's lifetime. Consider the timeline from to : initial and type checking identify candidate overloads, followed by resolution during semantic analysis where static binding fixes the exact implementations based on argument types, culminating in where the bound calls are embedded directly into the , ready for immediate execution without further intervention.

Implementation in Languages

In Rust

In Rust, static dispatch is primarily achieved through the use of traits in conjunction with , allowing the to resolve method calls at without relying on polymorphism. When a trait is implemented for specific types and used within generic functions or structs bounded by that trait, the Rust performs monomorphization, generating specialized versions of the code for each concrete type encountered during compilation. This process ensures that trait methods are inlined and optimized as if they were concrete function calls, avoiding any indirect lookup overhead associated with . A representative example involves defining a for drawable components and using it in a generic screen structure. Consider the following definition:
rust
pub [trait](/page/Trait) Draw {
    fn draw(&self);
}
This can be implemented for types, such as a Button:
rust
pub struct Button {
    pub width: u32,
    pub height: u32,
    pub label: [String](/page/String),
}

impl Draw for Button {
    fn draw(&self) {
        // Code to draw a button
    }
}
A Screen struct can then bound its components by the Draw trait:
rust
pub struct Screen<T: Draw> {
    pub components: Vec<T>,
}

impl<T> Screen<T> where T: Draw {
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}
Upon compilation with Button as T, the compiler monomorphizes the run method into a Screen<Button>-specific version, directly calling Button::draw without any at . This occurs for each unique type, demonstrating how static dispatch enables type-safe polymorphism through compile-time . Static dispatch plays a crucial role in 's ownership model by facilitating zero-cost abstractions, where high-level trait-based interfaces compile down to efficient, low-level without runtime penalties. This compile-time resolution aligns with Rust's borrow checker, ensuring and concurrency guarantees—such as preventing data races—through static analysis rather than dynamic checks, thus maintaining performance in concurrent programs. Static dispatch was formalized as a core feature in Rust 1.0, released on May 15, 2015, which established the language's foundation for safe, efficient including concurrency primitives built on traits and generics.

In C++

In C++, static dispatch occurs for non-virtual member functions (including static member functions), template instantiation, and overload resolution, all of which resolve function calls at compile time to generate efficient, type-specific code without runtime overhead. Static member functions, declared using the static keyword within a class, operate independently of any object instance and do not receive a this pointer, ensuring their invocation is bound directly to the class scope during compilation. Overload resolution, a core compiler process, evaluates candidate functions—including non-template overloads and template specializations—based on argument types, implicit conversions, and viability to select the most appropriate match before code generation. Template instantiation further enables this by substituting template parameters with concrete types at compile time, producing specialized implementations that support generic programming while maintaining static binding. The evolution of these mechanisms began with the C++98 standard (ISO/IEC 14882:1998), which introduced as the foundation for generics, allowing compile-time polymorphism and across types without dynamic resolution. This standard formalized syntax, including class and function , enabling early static dispatch for libraries and algorithms. Subsequent refinements in C++11 (ISO/IEC 14882:2011) enhanced expressiveness with features like for automatic type deduction, which simplifies usage in static contexts, and that can be captured in unevaluated contexts for , further integrating static dispatch into modern idioms. A representative example of template metaprogramming employing static dispatch is tag dispatching, often used to implement type-safe operations akin to variants by selecting behaviors based on type traits at compile time. For instance, to detect whether a type is constant, tags enable overload resolution among specialized templates:
cpp
#include <type_traits>

template <typename T>
struct TypeTag {};

namespace detail {
    template <typename T>
    std::true_type is_const(TypeTag<T const>);

    template <typename T>
    std::false_type is_const(TypeTag<T>);
}

template <typename T>
using is_constant = decltype(detail::is_const(std::declval<TypeTag<T>>()));
Here, the compiler resolves the is_const overload statically using the TypeTag wrapper, yielding std::true_type for constant-qualified types and std::false_type otherwise, which can extend to variant-like dispatching for type-safe visitation patterns. Integration with the (STL) exemplifies practical static dispatch, particularly in containers like std::vector<T>, which instantiates type-specific implementations at for optimizations such as contiguous allocation, element , and access tailored to T's properties. For example, operations like push_back resolve statically to invoke T's copy or move constructors, enabling constant-time performance guarantees (O(1) amortized for insertions) and specializations, such as std::vector<bool>, that pack bits for space efficiency without runtime type checks.

Performance and Trade-offs

Advantages

Static dispatch offers significant performance advantages over dynamic dispatch by resolving method calls at compile time, resulting in zero runtime overhead for type resolution and function selection. Unlike dynamic dispatch, which requires runtime lookups through mechanisms like virtual function tables (v-tables), static dispatch generates direct calls, eliminating the need for such indirection and avoiding associated memory accesses and branch predictions. This leads to smaller binary sizes, as no v-table data structures are required for polymorphic behavior, reducing the overall footprint of the executable. A key benefit is the opportunity for aggressive optimizations, particularly inlining, where the can substitute the called 's directly into the caller, removing function call overhead entirely. Inlining is facilitated because the exact is known during , allowing the optimizer to apply further transformations like constant propagation and . In terms of reliability, static dispatch enables early detection of type-related errors during , preventing runtime type mismatches that could crash programs or lead to . By resolving all polymorphic calls upfront, it ensures without runtime checks, providing deterministic execution paths that are verifiable before deployment. This compile-time validation offers developers faster feedback loops for , as issues surface immediately rather than during testing or , and simplifies optimizations by guaranteeing known types for analysis.

Limitations

Static dispatch, while offering performance benefits through compile-time resolution, imposes several constraints that can limit its applicability in certain scenarios. One primary limitation is its reliance on knowing concrete types at , which precludes the use of polymorphism for types that are not fully specified until execution. This makes static dispatch unsuitable for scenarios involving dynamic type hierarchies or interfaces where the exact implementing type is determined at , such as in systems or extensible architectures. A significant drawback is the potential for resulting from monomorphization, the process by which the generates specialized code for each unique type combination used with or . In , for instance, implementing a trait-bound for multiple types like u8 and produces distinct versions of the function, expanding the binary size proportionally to the number of instantiations. Similarly, in C++, instantiation creates separate code for each set of arguments across translation units, which can lead to duplicated code that is only merged at link time, exacerbating executable size in libraries with extensive generic usage. Compilation times are another notable limitation, as the must analyze and generate for all type during the build process. This overhead grows with the complexity and diversity of types, potentially slowing development workflows in large projects. In , excessive monomorphization can significantly extend build durations. C++ templates introduce additional complexity through two-phase name lookup and instantiation dependencies, further prolonging . Additionally, static dispatch restricts flexibility in or implementations due to language-specific rules. Rust's orphan rule, for example, prevents implementing foreign on foreign types unless one is owned by the developer, limiting reuse of existing libraries in generic contexts. In C++, while templates offer broad applicability, the need for explicit to avoid unwanted instantiations can complicate code maintenance and increase the risk of errors in generic designs. These constraints often necessitate workarounds like or careful type design to maintain modularity.

References

  1. [1]
    [PDF] CS 4110 – Programming Languages and Logics Lecture #22: Objects
    In Java, all methods (except for static methods) are dispatched dynamically. In C++, only virtual members are dispatched dynamically. Note that dynamic dispatch ...
  2. [2]
    Dynamic Dispatch in Object Oriented Languages
    Dynamic dispatch means method binding is determined at runtime based on the object's type. In C++, it occurs with virtual methods, and in Java, by default.
  3. [3]
    Principles of Programming Languages: Chapter 9
    The technical term for object-oriented method invocation is dynamic dispatch. In the programming languages literature, the term dynamic binding is often used as ...
  4. [4]
    [PDF] Dynamic Dispatch
    Most OOP languages: subclasses can change the behavior of superclass methods they do not override. Dynamic Dispatch 10 class A { boolean even(int x) { if (x == ...
  5. [5]
    [PDF] 9 Static Versus Dynamic Types
    That's called, naturally enough, static dispatch.) Dynamic dispatch is pretty powerful, because it makes it easy to adjust the behavior of objects by simply ...
  6. [6]
    Concepts, interfaces, and static dispatch - C++ to Rust Phrasebook
    In C++, static dispatch over an interface is achieved by implementing a template function or template method that interacts with the type using some expected ...
  7. [7]
    [PDF] The Development of the C Language - Nokia
    The C programming language was devised in the early 1970s as a system implementation language for the nascent Unix operating system. Derived from.
  8. [8]
    [PDF] A History of C++: 1979− 1991 - Bjarne Stroustrup
    Jan 1, 1984 · Support for object− oriented programming was not claimed until the provision of virtual functions in C++ in 1983. [Stroustrup,1984a]. 2.4 ...
  9. [9]
    Overload resolution - cppreference.com - C++ Reference
    Apr 26, 2025 · Overload resolution determines which of multiple overloaded functions to call by building, trimming, and analyzing a set of candidate functions ...
  10. [10]
    Generic Data Types - The Rust Programming Language
    Monomorphization is the process of turning generic code into specific code by filling in the concrete types that are used when compiled.
  11. [11]
    Two-phase name lookup - cppreference.com
    May 16, 2020 · Two-phase name lookup is the process of instantiating a template. First, syntax is checked at definition, then arguments are substituted at  ...
  12. [12]
    Function template - cppreference.com
    Apr 16, 2025 · In order to instantiate a function template, every template argument must be known, but not every template argument has to be specified. When ...(edit) Explanation · (edit) Explicit... · (edit) Implicit...
  13. [13]
    [PDF] Polymorphism
    Static versus dynamic dispatch. To determine which function to call: • Static dispatch uses the static type of the variable. • Dynamic dispatch uses the run ...
  14. [14]
    [PDF] Polymorphism - Cornell University
    Apr 29, 2001 · – ad hoc polymorphism (overloading). – parametric polymorphism. • template mechanisms (C++). • fully parametric code (ML). • bounded parameters ...
  15. [15]
  16. [16]
    Restrictions - USENIX
    Most are needed to prevent ambiguities that prevent us from selecting between dynamic and static dispatch at compile time. ... no dispatch table. next · up ...
  17. [17]
    Incremental overload resolution in object-oriented programming ...
    In this paper, we focus on static overloading in Featherweight Java, which is resolved at compile time based on the types of the method arguments.
  18. [18]
    Announcing Rust 1.0 | Rust Blog
    May 15, 2015 · We are very proud to announce the 1.0 release of Rust, a new programming language aiming to make it easier to build reliable, efficient systems.
  19. [19]
    When do we need Tag Dispatching in Template Metaprogramming?
    Oct 15, 2021 · I wanted to replicate a function overload resolution example to detect whether a given type is constant using the following: ... C++ Template ...
  20. [20]
  21. [21]
    Reducing indirect function call overhead in C++ programs
    Inlining. I-calls not only reduces the number of function calls, it exposes opportunities for other optimizations such as better register allocation, constant.
  22. [22]
    [PDF] Multi-Dispatch in the Java Virtual Machine: Design and ...
    Abstract. Mainstream object-oriented languages, such as. C++ and Java1, provide only a restricted form of polymorphic methods, namely single-receiver dis-.
  23. [23]
    Always-available static and dynamic feedback - ACM Digital Library
    Whereas dynamic typing enables rapid prototyping and easy experimentation, static typing provides early error detection and better compile time optimization. ...
  24. [24]
    Soft typing | Guide books - ACM Digital Library
    Static typing systems have two important advantages over dynamically typed systems: First, they provide important feedback to the programmer by detecting a ...
  25. [25]
    Traits: Defining Shared Behavior - The Rust Programming Language
    ### Summary of Static Dispatch, Traits, Monomorphization, and Limitations