Fact-checked by Grok 2 weeks ago

Dynamic dispatch

Dynamic dispatch is a fundamental mechanism in () that resolves calls at based on the actual type of the object, enabling polymorphism by selecting the most appropriate implementation from a rather than binding it at . This process, also known as late binding or resolution, allows subclasses to override superclass methods, ensuring that the correct version is invoked even when the object's type is only known dynamically. For example, if a of a superclass type holds a subclass instance, a call on that variable will execute the subclass's overriding method. In contrast to static dispatch, where method binding occurs at compile time based on the declared type, dynamic dispatch provides greater flexibility for extensible software design, supporting key OOP principles like inheritance and encapsulation. It is essential for achieving runtime polymorphism, allowing code to operate on abstract interfaces or base classes while adapting to concrete implementations at execution. This mechanism is implemented differently across languages: in C++, it requires the virtual keyword for methods and uses pointers or references for invocation; in Java, it applies by default to non-static, non-final instance methods on reference types. Even in database systems like Oracle SQL object types, dynamic dispatch navigates the type hierarchy upward from the instance's type to find the nearest overriding implementation. Dynamic dispatch is commonly realized through virtual function tables (vtables), data structures where each class maintains a table of function pointers to its methods, and each object includes a hidden pointer (vptr) to its class's vtable for quick lookup. This approach ensures efficient O(1) dispatch time while handling hierarchies, though it introduces slight overhead compared to static calls due to . Languages like C++ and rely on this for core polymorphic behavior, making dynamic dispatch a of modern paradigms.

Introduction

Definition and purpose

Dynamic dispatch is the process of selecting which method implementation to invoke based on the type of the object on which the method is called. This mechanism, also known as late binding or virtual method invocation, enables runtime polymorphism by resolving method calls dynamically rather than at . In , it allows a single method name to exhibit different behaviors depending on the actual type of the object, even when invoked through a reference or pointer to a superclass or . The primary purpose of dynamic dispatch is to support hierarchies and implementations, allowing subclasses to override superclass methods while maintaining a interface. This facilitates flexible by decoupling the caller from specific implementations, promoting modular and extensible designs without requiring tight coupling between classes. By enabling such polymorphism, dynamic dispatch ensures that code written against a base type can seamlessly accommodate derived types added later, enhancing in large software systems. Key benefits include achieving late binding, which contrasts with compile-time decisions by permitting runtime adaptability and supporting implicit extensibility in class hierarchies. This approach avoids code duplication and allows for more patterns, where the same invocation logic applies across diverse object types. Most object-oriented languages implement dynamic dispatch as single dispatch, resolving the method based solely on the receiver object's type. A illustration of dispatch can be seen in the following for a call e.m():
1. Evaluate e to value v.
2. Let C be the [class](/page/Class) of v.
3. Search C for [method](/page/method) m(); if not found, repeat with C's superclass.
4. Execute m() with this bound to v.
This process ensures the most specific implementation is selected at , demonstrating how dynamic dispatch resolves calls on a base to a derived class's overridden .

Historical development

The roots of dynamic dispatch trace back to early programming languages that emphasized flexible function application and runtime behavior, such as (developed by John McCarthy in 1958), where features like symbols, property lists, and the eval function enabled resolution of expressions, laying foundational ideas for later polymorphic mechanisms. However, dynamic dispatch as a core mechanism in was pioneered in 67, introduced in 1967 by and , where virtual procedures were introduced for simulation purposes, allowing method calls to be resolved at based on object types to support and polymorphism in the first object-oriented language. The 1970s saw dynamic dispatch gain prominence in object-oriented paradigms through Smalltalk, conceived by and his team at PARC starting in 1972. Smalltalk popularized as a core mechanism, where objects respond to messages via dynamic lookup at , influencing the view of computation as communication between autonomous entities and embedding dispatch deeply into the language's design. Building on these ideas, introduced virtual functions in C++ in 1983, extending C with runtime polymorphism to support large-scale software development at , which became a staple for efficient, low-level . In the 1990s, , led by at and first released in 1995, inherited dynamic dispatch from C++ but made all non-static instance methods virtual by default, emphasizing platform independence through the while simplifying polymorphism for widespread adoption. Concurrently, multiple dispatch—a generalization allowing resolution on multiple arguments—emerged in the Object System (CLOS), developed in the late 1980s and standardized in 1994, integrating generic functions with runtime method selection for flexible, extensible systems. This approach influenced later languages like , released in 2012 by Jeff Bezanson, , Viral Shah, and , where multiple dispatch is central to scientific computing, enabling type-specialized functions for performance-critical applications. Key milestones include the formalization of C++'s virtual functions in the ISO/IEC 14882 standard of 1998, ensuring portable polymorphism across implementations, and Rust's introduction of trait objects for dynamic dispatch upon its 1.0 stable release in 2015, providing safe, memory-managed alternatives to traditional virtual tables in .

Core Concepts

Static versus dynamic dispatch

resolves method calls at based on the declared static types of the arguments, employing techniques such as or generics to select the appropriate implementation. This approach enables early binding, where the determines the exact function to invoke without overhead, resulting in efficient execution but limited flexibility for handling polymorphism that emerges at . In contrast, dynamic dispatch defers resolution until runtime, selecting the method based on the actual dynamic types of the objects involved, which supports in inheritance hierarchies and accommodates in dynamically typed languages. This mechanism allows for runtime polymorphism, where the behavior of a call can vary depending on the concrete object type, even if the reference is of a supertype. The following illustrates the difference: in , a direct call is resolved immediately by the based on the parameter types.
[function](/page/Function) processShape(shape: [Shape](/page/Shape)) {
    [return](/page/Return) shape.area();  // Static: resolved to Shape.area() if no overloads
}
For dynamic dispatch, the call invokes the overridden method via a lookup, such as through a .
[function](/page/Function) processShape(shape: [Shape](/page/Shape)) {
    [return](/page/Return) shape.area();  // Dynamic: resolved to actual type's area() at runtime
}
Static dispatch generally provides superior performance due to the absence of and compile-time optimizations, along with stronger through early error detection, whereas dynamic dispatch introduces overhead but enhances flexibility for designing extensible systems, such as those involving plugins or frameworks where implementations may be added post-compilation. Virtual method tables serve as a common implementation for efficient dynamic dispatch in many languages. Developers typically employ in performance-critical sections of code where the types are known and fixed at , while opting for dynamic dispatch when defining abstract interfaces that require adaptability across diverse implementations.

Single versus multiple dispatch

Single dispatch refers to a form of dynamic dispatch in which the selection of the appropriate is determined solely by the type of the , or first , of the method call. This mechanism is prevalent in class-based languages, where each class maintains its own set of methods, and the dispatch resolves to the method defined in the subclass based on the actual object type. For instance, in a call like car.collide(bike), the method chosen depends only on the type of car, regardless of bike's type. In contrast, multiple dispatch extends this by selecting the method based on the runtime types of two or more arguments, enabling more nuanced polymorphism that considers interactions between multiple objects. This approach is particularly useful for operations where the behavior depends symmetrically on all involved types, such as arithmetic operations between different numeric types (e.g., adding an to a versus float to float) or between diverse entities (e.g., collide(car, bike) dispatching differently from collide(bike, car) based on both types). provides greater expressiveness than single dispatch, as it generalizes the latter—any single-dispatch scenario can be simulated by ignoring additional arguments—while allowing for more flexible method resolution without relying on workarounds like explicit type checks. Conceptually, single dispatch aligns well with hierarchical structures in object-oriented languages, where methods are overridden along a inheritance chain to specialize behavior for subclasses, promoting encapsulation around the receiver object. Multiple dispatch, however, better accommodates symmetric or multi-party relationships that do not privilege one argument, such as binary operators or generic functions that operate on peer arguments, reducing the need for visitor patterns or conditional logic in single-dispatch systems. To illustrate single dispatch in pseudocode, consider a method call on an object:
let obj = new Subclass();  // runtime type is Subclass
obj.[method](/page/Method)(arg);           // dispatches to Subclass.method based on obj's type
Here, the method resolution occurs using the dynamic type of obj alone. For multiple dispatch, the pseudocode involves a call with multiple arguments:
collide(car: [Car](/page/Car), bike: Bike);  // dispatches to method matching both Car and Bike types
The system selects the most specific method whose parameter types are compatible with both arguments' runtime types. Most class-based object-oriented languages, such as C++ and , implement single dispatch as their primary polymorphism mechanism. is natively supported in languages like via its CLOS (Common Lisp Object System), where generic functions dispatch on all arguments, and , which uses multiple dispatch as a core feature for all function calls.

General Mechanisms

Virtual method tables

Virtual method tables, often abbreviated as vtables, serve as a fundamental mechanism for implementing single dynamic dispatch in object-oriented languages with compiled code generation. A vtable is a per-class data structure consisting of an array of function pointers that map virtual method names or signatures to their concrete implementations. Each instance of a class contains a hidden pointer, known as the virtual pointer or vptr, typically located at the beginning of the object's memory layout, which references the appropriate vtable for that class. This setup enables runtime resolution of method calls based on the actual type of the object, rather than its compile-time declared type. The dispatch process occurs at as follows: when a virtual method is invoked on an object through a base pointer or reference, the first loads the vptr from the object's layout. This vptr points to the vtable, from which the system indexes into the specific slot corresponding to the method's offset—determined at based on the method's declaration order in the —and retrieves the for execution. This indirection adds a small overhead but ensures polymorphic . In implementations like the C++ ABI, the vtable may include additional entries such as offset-to-top for virtual base adjustments and pointers to (RTTI), enhancing support for more complex scenarios. Inheritance is handled by extending and modifying vtables in derived classes. A derived class inherits the vtable layout from its base class, copying entries for non-overridden methods while replacing slots for overridden virtual methods with pointers to the new implementations. For single inheritance, the vtable remains straightforward, with the derived class's vtable building directly upon the base's. In cases of multiple inheritance, complications arise due to multiple base classes potentially contributing virtual methods; the Itanium C++ ABI addresses this by generating secondary vtables for non-primary bases and employing thunk functions—small assembly stubs that adjust the implicit this pointer to align with the correct subobject offset before delegating to the actual method implementation. These thunks are stored in the vtable entries, ensuring correct dispatch across the inheritance graph without requiring pointer adjustments at every call site. To illustrate, consider the memory layout of an object in a single . The object begins with the vptr (e.g., an 8-byte pointer on 64-bit systems) pointing to the class's vtable, followed by non-static data members. The vtable itself is an array starting with metadata like the offset-to-top (0 for the most derived class) and RTTI pointer, then slots for virtual methods: the base class's methods first, followed by any new or overridden ones in the derived class. For example:
Object Layout:
+-------------+
| vptr  ----> |  (points to VTable)
+-------------+
| data1      |
+-------------+
| data2      |
+-------------+

VTable:
+-------------+  
| offset-to-top (0) |
+-------------+  
| RTTI ptr    |  
+-------------+  
| base_method1|  (ptr to impl)
+-------------+  
| base_method2|  (ptr to overridden impl in derived)
+-------------+  
| derived_method | (ptr to new impl)
+-------------+  
This layout allows efficient access: a call to base_method2() loads the vptr, indexes to the second slot, and jumps to the derived if overridden. Vtables are central to languages like C++, where the Itanium ABI standardizes their construction for compatibility across compilers, and , where the JVM implementation uses vtables (or method tables) per class to support invokevirtual bytecode for dynamic dispatch—though the JVM specification itself does not mandate this exact mechanism, leaving room for alternatives in other implementations. In contrast, interpreted languages tend to rely less on precomputed vtables, favoring dictionary-based lookups at runtime for greater flexibility.

Message passing and dynamic lookup

In the message passing paradigm, objects communicate by sending messages, which are essentially invocations, to other objects. The receiver object determines the appropriate response at by searching for a matching in its method dictionary, a typically implemented as a keyed by message selectors (unique identifiers for methods). This approach, foundational to pure object-oriented languages, enables flexible interactions without requiring compile-time knowledge of the receiver's type. The dynamic lookup process occurs entirely at : upon receiving a , the hashes the selector to probe the receiver's dictionary. If no match is found, it traverses the (superclasses) or prototype chain until a suitable is located or an is raised. This search-based resolution supports metaclasses, where classes themselves are objects that can define meta-methods, facilitating meta-programming such as modifications. Unlike precomputed pointer tables, this mechanism avoids fixed structures, allowing methods to be added or overridden dynamically during execution. Polymorphism is handled through late binding, where the same message can elicit different behaviors based on the receiver's current state or class, even if methods are introduced post-deployment. For instance, a message like display might render a rectangle differently from a circle, resolved solely at invocation time. This runtime flexibility contrasts with static approaches by permitting on-the-fly extensions without recompilation. The lookup can be illustrated in pseudocode, adapted from early Smalltalk implementations:
send message to receiver:
  currentClass = receiver's class
  while currentClass is not nil:
    method = currentClass's methodDictionary lookup: message selector
    if method found:
      create new context with method, receiver, and arguments
      execute method in context
      return result
    currentClass = currentClass's superclass
  raise doesNotUnderstand error
This process is central to languages like Smalltalk, where all interactions are message-based; , which uses selectors for method resolution; and , where descriptors customize attribute and method lookups to achieve similar dynamic .

Language Implementations

C++

In C++, dynamic dispatch is primarily achieved through , which allow polymorphism by enabling derived es to override base methods. The keyword is used in the declaration of a non-static member function within a base to mark it as , supporting dynamic dispatch via pointers or to the base . When a derived defines a function with the same name, parameter types, cv-qualifiers, and ref-qualifiers as a in the base , it overrides the base version, either implicitly or explicitly using the override specifier (introduced in C++11 for safety). This mechanism ensures that the correct derived implementation is invoked at , regardless of the static type of the pointer or . The implements virtual functions using virtual method tables (vtables), one per class containing virtual functions, and a virtual pointer (vptr) embedded in each object instance. For each class with virtual functions, the generates a vtable—a static array of s corresponding to the virtual functions in declaration order. During object construction, the vptr (typically the first member of the object layout) is initialized to point to the appropriate vtable for that class's type. At , when a virtual function is called through a base pointer or reference, the system dereferences the vptr to access the vtable and jumps to the function pointer at the correct offset, resolving the call based on the dynamic type of the object. This process adds a small overhead but enables flexible polymorphism. Pure virtual functions, declared using the = 0 syntax (e.g., virtual void func() = 0;), further extend this by defining abstract base classes that cannot be instantiated directly. A class is abstract if it declares or inherits at least one pure virtual function without providing an implementation, serving as an interface for derived classes to implement. Derived classes must override all pure virtual functions from the base to become concrete and instantiable, promoting a clear hierarchy for polymorphism. The following code example illustrates a simple base-derived hierarchy with an abstract base class Shape, demonstrating upcasting and dynamic dispatch resolution:
cpp
class Shape {  // Abstract base class
public:
    virtual ~Shape() = [default](/page/Default);  // Virtual destructor for proper cleanup
    virtual [double](/page/Double) area() const = 0;  // Pure virtual function
};

class Circle : [public](/page/Public) Shape {
private:
    [double](/page/Double) radius;
public:
    Circle([double](/page/Double) r) : radius(r) {}
    [double](/page/Double) area() const override {
        return 3.14159 * radius * radius;
    }
};

class Rectangle : [public](/page/Public) Shape {
private:
    [double](/page/Double) width, height;
public:
    Rectangle([double](/page/Double) w, [double](/page/Double) h) : width(w), height(h) {}
    [double](/page/Double) area() const override {
        return width * height;
    }
};

void printArea(const Shape& s) {  // Accepts base reference (upcasting)
    std::cout << "Area: " << s.area() << std::endl;  // Dynamic dispatch
}

int main() {
    Circle c(5.0);
    Rectangle r(4.0, 6.0);
    printArea(c);  // Calls Circle::area()
    printArea(r);  // Calls Rectangle::area()
    return 0;
}
In this example, calls to area() through the Shape reference resolve to the derived class implementations at runtime, showcasing polymorphism without knowing the exact type. C++ supports multiple inheritance, allowing a derived class to inherit from multiple base classes, but this introduces challenges like the diamond problem, where ambiguity arises from a shared base class. Consider a hierarchy where classes B and C both inherit from A, and D inherits from both B and C; without mitigation, D would contain two separate subobjects of A, leading to duplicate data members and ambiguous member access (e.g., which A::data to use). Virtual inheritance resolves this by using the virtual keyword in the inheritance list of intermediate classes (e.g., class B : public virtual A), ensuring a single shared subobject of the virtual base class A in D, thus eliminating duplication and ambiguity. However, virtual inheritance incurs costs, such as increased object sizes due to additional vtable pointers or offsets (typically 4-8 bytes per virtual base on common architectures) and slightly more complex construction semantics, where the virtual base is constructed only once by the most derived class. As an alternative to virtual functions for achieving polymorphism without runtime dispatch, C++ provides the Curiously Recurring Template Pattern (CRTP), a compile-time technique for static polymorphism. In CRTP, a base class template is instantiated with the derived class as a template parameter (e.g., template <typename Derived> class Base; class Derived : public Base<Derived> {}), allowing the base to access and call derived-specific methods via static_cast<Derived*>(this) at compile time. This resolves function calls statically, avoiding vtable overhead and enabling optimizations like inlining, while still providing a polymorphic interface. CRTP is particularly useful in performance-critical scenarios where dynamic dispatch is undesirable.

Java

In Java, dynamic dispatch is realized through virtual methods executed on the Java Virtual Machine (JVM), ensuring platform-neutral runtime polymorphism across diverse hardware and operating systems. All non-static, non-final, and non-private instance methods are virtual by default, permitting subclasses to override superclass implementations for behavior variation based on the actual object type at runtime. Interfaces reinforce this by declaring abstract methods that implementing classes must provide, thereby enforcing dispatch to concrete realizations and facilitating modular, extensible designs. The JVM orchestrates dynamic dispatch via the invokevirtual bytecode instruction, which resolves method calls during execution by consulting method tables embedded in class metadata within the runtime constant pool. This process links symbolic references to direct method implementations at load time or lazily upon first invocation, while the JVM's support for dynamic class loading enables seamless integration of new classes—loaded via ClassLoader—into the dispatch hierarchy without halting the application. A representative example demonstrates polymorphism through interface implementation and method overriding. Consider the Shape interface with an abstract area() method, implemented by Circle and Rectangle classes:
java
interface Shape {
    double area();
}

class Circle implements Shape {
    private double radius;
    public Circle(double radius) { this.radius = radius; }
    @Override
    public double area() { return Math.PI * radius * radius; }
}

class Rectangle implements Shape {
    private double width, height;
    public Rectangle(double width, double height) { this.width = width; this.height = height; }
    @Override
    public double area() { return width * height; }
}

// Polymorphic usage
Shape shape1 = new Circle(5.0);
Shape shape2 = new Rectangle(4.0, 6.0);
System.out.println(shape1.area()); // Outputs ≈78.54 (Circle's implementation)
System.out.println(shape2.area()); // Outputs 24.0 (Rectangle's implementation)
Here, references of type Shape invoke area() polymorphically, with the JVM dispatching to the overriding method of the underlying object instance via invokevirtual. Introduced in 8, default methods in interfaces provide concrete implementations, enabling evolution by adding functionality to existing interfaces without requiring recompilation or breaking binary compatibility for prior implementers. Resolution of default methods during dispatch favors the most specific implementation in inheritance hierarchies; for instance, if a class implements multiple interfaces with override-equivalent default methods, a compile-time occurs unless the class explicitly overrides the conflicting method to resolve the ambiguity. Java's supports meta-level dynamic dispatch by enabling inspection and invocation of through the java.lang.reflect package. The Method.invoke(Object obj, Object... args) call executes a on a specified object instance (or null for static ), performing dynamic resolution akin to normal dispatch but with additional overhead for ; any underlying exceptions are encapsulated in an InvocationTargetException for extraction via getCause(). This facility is essential for tools like frameworks and dependency injectors that manipulate code generically at .

Python

In Python, dynamic dispatch is achieved through a flexible mechanism involving descriptors and the __getattribute__ method, making all instance methods implicitly virtual. When an attribute is accessed on an instance, such as obj.method, the object.__getattribute__ method performs the lookup by first checking the class's __dict__ for descriptors. Functions defined in a class act as non-data descriptors, and their __get__ method binds the instance as the first argument (self), transforming the function into a bound method at runtime. This process ensures that method resolution occurs dynamically, allowing polymorphism based on the actual object type without explicit virtual declarations. For classes with , employs the Method Resolution Order (MRO) to determine the sequence in which base are searched for attributes and methods, preventing ambiguities in scenarios. The MRO is computed using the algorithm, introduced in 2.3 for new-style , which produces a monotonic that respects local precedence (superclasses appear before subclasses) and the order of base in the list. For example, in a hierarchy where A inherits from B and C, both of which inherit from D, the MRO for A would be [A, B, C, D], ensuring D's methods are only called after B and C if not overridden. This runtime computation of the MRO, accessible via the __mro__ attribute, enables consistent method dispatch across complex hierarchies. The super() function facilitates cooperative by allowing es to invoke s from the next in the MRO dynamically, promoting without hardcoding parent names. In a , super().method() resolves the appropriate at based on the MRO, enabling each to extend or modify behavior from subsequent ones. Consider the following example demonstrating resolution in a setup:
python
[class](/page/Class) Base:
    def __init__(self):
        print("Base init")

class Left(Base):
    def __init__(self):
        super().__init__()
        print("Left init")

class Right(Base):
    def __init__(self):
        super().__init__()
        print("Right init")

class Derived(Left, Right):
    def __init__(self):
        super().__init__()
        print("Derived init")

Derived()  # Outputs: Base init\nLeft init\nRight init\nDerived init
Here, super() in Derived calls Left.__init__, which chains to Right.__init__ and then Base.__init__, illustrating cooperative dispatch along the MRO [Derived, Left, Right, Base]. Special methods, often called methods (e.g., __add__, __eq__), enable and follow single dispatch semantics, where the method is selected based on the type of the left . For instance, implementing __add__(self, other) in a allows custom behavior for the + , such as adding two custom objects; if unsupported, attempts the reflected method __radd__ on the right . This provides duck-typed polymorphism for operators without multi-argument dispatch. An example is a simple vector :
python
class Vector:
    def __init__(self, x, y):
        [self](/page/Self).x, [self](/page/Self).y = x, y

    def __add__(self, other):
        if isinstance(other, [Vector](/page/Vector)):
            return [Vector](/page/Vector)([self](/page/Self).x + other.x, [self](/page/Self).y + other.y)
        return NotImplemented

    def __str__(self):
        return f"[Vector](/page/Vector)({[self](/page/Self).x}, {[self](/page/Self).y})"

v1 = [Vector](/page/Vector)(1, 2)
v2 = [Vector](/page/Vector)(3, 4)
[print](/page/Print)(v1 + v2)  # Outputs: [Vector](/page/Vector)(4, 6)
This dispatch occurs at , binding the left operand's method. Metaclasses, with type as the default, allow customization of class creation, including alterations to dispatch mechanisms before instances are made. By subclassing type and overriding methods like __new__ or __prepare__, a metaclass can modify the class's namespace or attribute resolution at creation time. For example, a custom metaclass can inject dynamic dispatch logic into method lookups:
python
class DispatchMeta(type):
    def __new__(cls, name, bases, namespace):
        # Customize dispatch by adding a descriptor
        class DynamicMethod:
            def __get__(self, instance, owner):
                if instance is None:
                    return self
                return lambda *args, **kwargs: print(f"Dynamic dispatch for {name}")
        namespace['dynamic_method'] = DynamicMethod()
        return super().__new__(cls, name, bases, namespace)

class MyClass(metaclass=DispatchMeta):
    pass

obj = MyClass()
obj.dynamic_method()  # Outputs: Dynamic dispatch for MyClass
This approach enables advanced behavior modifications, such as proxying method calls, integrated into the class's dispatch process.

Rust

In Rust, dynamic dispatch is achieved primarily through trait objects, which enable polymorphism while maintaining the language's emphasis on and zero-cost abstractions. A trait object is formed using the dyn keyword, such as Box<dyn [Trait](/page/Trait)> for heap-allocated or &dyn [Trait](/page/Trait) for borrowing, allowing values of different types that implement the same to be treated uniformly at . These trait objects are implemented as fat pointers, consisting of a pointer to the data and a pointer to a (vtable) that stores function pointers for the 's methods. This structure facilitates dynamic dispatch, where the specific method implementation is resolved at by consulting the vtable, contrasting with compile-time monomorphization in . The compiler automatically generates vtables for traits used in dynamic dispatch, embedding them in the binary as static data structures containing pointers to the appropriate method implementations for each type. Unlike class-based languages, Rust does not support ; instead, it promotes composition through , allowing types to implement multiple and enabling flexible, modular code without diamond problems. For example, a Drawable might define a draw method, implemented by structs like Circle and Rectangle. A heterogeneous collection, such as Vec<Box<dyn Drawable>>, can then store instances of both, invoking draw via dynamic dispatch at runtime.
rust
trait Drawable {
    fn draw(&self);
}

struct Circle;
impl Drawable for Circle {
    fn draw(&self) {
        println!("Drawing a circle");
    }
}

struct Rectangle;
impl Drawable for Rectangle {
    fn draw(&self) {
        println!("Drawing a rectangle");
    }
}

fn main() {
    let mut shapes: Vec<Box<dyn Drawable>> = Vec::new();
    shapes.push(Box::new(Circle));
    shapes.push(Box::new(Rectangle));
    for shape in shapes {
        shape.draw();  // Dynamic dispatch via vtable
    }
}
This example demonstrates how trait objects enable polymorphism in collections, with the compiler inserting vtable lookups for method calls. For a to be usable with dyn, it must be object-safe (also termed dyn-compatible), meaning it satisfies specific criteria to ensure calls on trait objects are well-defined without knowledge of the concrete type at . A is object-safe if it does not require Self: Sized and all its are object-safe: each must lack generic type parameters, use a fixed type (like &self or &mut self), and avoid returning Self or using Self in argument positions except as the . Violations, such as a returning Self, prevent to dyn and trigger errors; in such cases, alternatives like enums provide with exhaustive for without runtime overhead. Advanced usage involves unsafe coercion to form trait objects from raw pointers, which is necessary for foreign function interfaces (FFI) or low-level memory management but carries significant risks. Using std::ptr::from_raw_parts or from_raw_parts_mut, a raw data pointer can be combined with a raw vtable pointer to construct a raw pointer to dyn Trait; this must then be safely converted to a reference or smart pointer. Such operations are inherently unsafe, as they bypass Rust's borrow checker, requiring the programmer to ensure the pointers are valid, properly aligned, and respect lifetimes—the vtable's lifetime must outlive the data to prevent use-after-free errors. Incorrect usage can lead to undefined behavior, emphasizing Rust's design philosophy of explicit unsafety for performance-critical code.

Smalltalk

In Smalltalk, dynamic dispatch is realized through a pure message-passing paradigm within a unified object model where everything is an object, including primitives, classes, and metaclasses. Methods are not invoked directly but are instead messages sent to a receiver object, which performs runtime lookup in the method dictionaries of its class and superclass chain to resolve the appropriate implementation. This late binding ensures that the specific method executed depends on the receiver's class at runtime, enabling polymorphism without explicit keywords like "virtual." The dispatch process begins when a message selector (e.g., #drawOn:) and arguments are sent to the receiver; if no matching method is found after traversing the inheritance hierarchy, Smalltalk raises a DoesNotUnderstand error by sending the doesNotUnderstand: message to the receiver, carrying a Message object with the selector and arguments. This mechanism supports meta-level extensions, such as dynamic method addition or proxying, allowing reflective interventions at runtime. Additionally, blocks—first-class objects representing closures with lexical scoping—facilitate deferred execution and are integral to control structures, capturing context via messages like value: or valueWithArguments:. A representative example illustrates polymorphism in a simple class hierarchy. Consider a base class Shape with subclasses Circle and Rectangle, where the #area message yields different computations:
smalltalk
Shape subclass: #Circle
    instanceVariableNames: 'radius'
    classVariableNames: ''
    package: 'Shapes'.

Circle >> area
    ^ Float pi * radius squared.

Shape subclass: #Rectangle
    instanceVariableNames: 'width height'
    classVariableNames: ''
    package: 'Shapes'.

Rectangle >> area
    ^ width * height.
Sending #area to a Circle instance computes πr², while the same message to a Rectangle returns width × height, demonstrating runtime resolution without static type checks. Smalltalk employs single inheritance, with all classes descending from Object via the abstract root Behavior, which defines the protocol for class creation and method management. Mixin-like behavior is achieved through traits (in modern implementations like Pharo) or by composing subclasses and dynamically adding methods to classes at runtime using metaclasses—objects whose instances are classes themselves, enabling modifications such as class-side methods or instance variable additions without recompilation. The Metaclass hierarchy parallels the regular class hierarchy, supporting reflective operations like inspecting or altering method dictionaries. Smalltalk's emphasis on late binding and has profoundly influenced modern languages, including and , by promoting dynamic, introspective designs that prioritize runtime flexibility and live programming environments over static declarations.

Common Lisp

In , dynamic dispatch is implemented through the Common Lisp Object System (CLOS), which provides support for on the classes or identities of all arguments to a . A serves as an extensible for operations, where the specific behavior is determined at runtime by selecting and combining applicable methods based on argument types. This approach decouples the definition of operations from specific classes, enabling flexible, multi-argument polymorphism. Generic functions are defined using the defgeneric macro, which specifies the function name, lambda list, and optional declarations or method combinations, without providing a body. Methods for a generic function are then added via the defmethod macro, where each method specializes one or more arguments on a (using a class name as the specializer) or on the of an object (using (eql object)). This specialization enables , as the system considers the combination of all argument specializers to determine applicability. For instance, methods can be defined to handle different pairs of argument es, such as numeric types or user-defined es, allowing the generic function to resolve to the most appropriate implementation dynamically. The dispatch algorithm in CLOS proceeds in several steps to compute and execute an . First, the uses compute-applicable-methods to identify all methods whose specializers match the es of the supplied arguments, producing a list of applicable methods. These methods are then sorted from most to least specific based on the class precedence lists of the argument es. Finally, the sorted list is combined into an using the 's method combination type, which by default is the standard method combination. This combination incorporates primary methods (the core implementations) along with optional :before, :after, and :around methods, enabling aspect-oriented programming-like behavior: :before methods run first (most specific to least) without returning values, followed by the primary method, then :after methods (least to most specific), with :around methods wrapping the entire sequence and allowing control over proceeding to the next method via call-next-method. If no applicable methods exist, an error is signaled. To illustrate multiple dispatch resolution, consider a generic function add that extends arithmetic addition to include user-defined types, such as a simple vector class representing one-dimensional vectors:
lisp
(defclass vector ()
  ((components :initarg :components :reader components)))

(defgeneric add (x y))

(defmethod add ((x number) (y number))
  (+ x y))

(defmethod add ((x vector) (y vector))
  (make-instance 'vector
                 :components (mapcar #'+ (components x) (components y))))

(defmethod add ((x number) (y vector))
  (make-instance 'vector
                 :components (mapcar (lambda (c) (+ x c)) (components y))))

(defmethod add ((x vector) (y number))
  (add y x))  ; Leverage symmetry
Calling (add 5 (make-instance 'vector :components '(1 2 3))) dispatches to the number-vector method, producing a vector (6 7 8), while (add (make-instance 'vector :components '(1 2)) (make-instance 'vector :components '(3 4))) uses the vector-vector method to yield (4 6). This demonstrates how dispatch resolves based on the combined argument classes. CLOS supports through its metaobject protocol (MOP), where classes, s, and methods are themselves instances of metaclasses, allowing programmatic inspection and modification. The compute-applicable-methods can be specialized or extended to implement custom dispatch logic, such as dispatching on predicates beyond classes or integrating domain-specific applicability rules. For example, users can define a subclass of standard-generic-function and override MOP methods to alter how applicable methods are computed, enabling tailored dispatch without altering the core system. Integration with Common Lisp's macro system allows developers to choose between compile-time and runtime dispatch for performance-critical code. While CLOS dispatch is inherently dynamic and occurs at runtime, macros can analyze argument types at compile time (using tools like cl-environments for environment access) to generate specialized inline code or select methods early, bypassing full invocation. Conversely, the compile-all-dispatch-code macro in some implementations precompiles dispatch tables for known s, optimizing runtime resolution while preserving CLOS's extensibility for cases where types are unknown until execution. This hybrid approach leverages Lisp's to blend static optimizations with dynamic flexibility.

Performance Considerations

Runtime overhead

Dynamic dispatch incurs runtime overhead through several mechanisms, primarily the indirection required for method resolution. In implementations using virtual method tables (vtables), such as in C++, a polymorphic call necessitates loading the virtual pointer (vptr) from the object, indexing into the vtable to obtain the target , and executing an indirect . This process adds approximately two extra accesses and 2-3 instructions compared to a direct static call, contributing to increased latency, especially if the vptr or vtable resides outside the . Space overhead is another key cost. On 64-bit architectures, each object requires an additional 8 bytes for the vptr, pointing to its 's vtable. Vtables themselves are allocated per and consist of an of function pointers, with size scaling linearly with the number of methods—typically 8 bytes per entry, resulting in hundreds of bytes for classes with dozens of methods. These structures are shared across instances but multiply with proliferation in large hierarchies. For , as in Common Lisp's CLOS, the overhead escalates due to the need to evaluate all argument types combinatorially to identify applicable methods and select the most specific one. Without caching, this can degenerate to time relative to the number of defined methods for a , far exceeding the constant-time lookup of . Empirical benchmarks underscore these costs in polymorphic hot paths. In C++ applications, dispatch operations can contribute significantly to execution time in code with heavy polymorphism, leading to slowdowns relative to equivalent scenarios. Factors amplifying this include cache misses in deep hierarchies, where scattered vtable accesses increase , and in managed languages like , where garbage collection pauses interrupt execution during object-intensive polymorphic operations.

Optimization techniques

Devirtualization is a optimization that replaces dynamic virtual calls with direct static calls when the receiver type can be determined through static analysis. In C++, the compiler performs devirtualization by unifying virtual table loads across call sites represented by different static single assignment () values, enabling more precise indirect call resolution and reducing indirection. The Java JVM applies devirtualization using analysis and type profiles to demote virtual or calls to special invocations, particularly for monomorphic sites where a single receiver type dominates, allowing subsequent inlining. This technique has been shown to improve efficiency in just-in-time compilers by up to 20-30% in call-heavy benchmarks through targeted direct method invocation. Inline caching addresses the overhead of dynamic dispatch in interpreted or JIT-compiled languages by storing recent type and target information directly at call sites, enabling fast-path execution for repeated invocations on similar types. In the V8 JavaScript engine, inline caches leverage hidden classes to map object structures to property access handlers, reducing instruction counts by 15% and execution time by 17% on average across web libraries like React by minimizing cache misses. PyPy's just-in-time compiler uses polymorphic inline caches to track multiple common receiver types at call sites, accelerating dynamic method dispatch in Python code by specializing stubs based on observed type histories during tracing. Type involves generating type-specific code versions at or to eliminate generic dispatch logic for prevalent cases. The JIT compiler specializes virtual call sites based on type profiles, producing monomorphic code paths for the most frequent receiver types (typically up to two or three per site), which facilitates aggressive inlining and reduces branch overhead. employs run-time type by rewriting bytecode instructions for observed operand types, adding lightweight guards to maintain safety and achieving average speedups of 1.2x on architectures, with peaks up to 2.45x in number-crunching workloads by removing redundant type checks. Guarded calls insert conditional runtime checks to speculatively devirtualize dynamic invocations, falling back to general dispatch if assumptions fail. In systems like the .NET runtime, guarded devirtualization clones code paths with type-specific guards, enabling direct calls for predicted concrete types while preserving correctness through deoptimization on mismatches. In , explicit conditional branching on trait object types allows manual devirtualization, where known type cases invoke static methods directly, bypassing vtable lookups. These techniques often rely on profile-guided optimization (PGO), which uses execution traces to prioritize hot paths, but introduce trade-offs such as potential slowdowns on unprofiled inputs due to biased code layout and reduced portability across environments, as specialized binaries may underperform without matching profiles. Multiple dispatch exacerbates these challenges by requiring joint type analysis for argument combinations, limiting devirtualization opportunities compared to single-receiver cases.

References

  1. [1]
    [PDF] Subclassing and Dynamic Dispatch
    This lecture is about dynamic dispatch: how a call o.m() may actually invoke the code of different methods, all with the same name m, depending on the ...
  2. [2]
    [PDF] 15-312 Lecture on Dynamic Dispatch
    Mar 4, 2013 · 15-312: Dynamic Dispatch. I. Cervesato. 15-312 Lecture on. Dynamic ... of object-oriented programming. Objects that differ at most by the ...
  3. [3]
    Dynamic Dispatch in Object Oriented Languages
    Dynamic Dispatch means that the binding of the method is determined at run time depending on the type of the object pointed to by pa. Static Dispatch in C++.Missing: science | Show results with:science
  4. [4]
    2.3 Inheritance in SQL Object Types - Oracle Help Center
    Dynamic method dispatch refers to the way that method calls are dispatched to the nearest implementation at run time, working up the type hierarchy from the ...<|separator|>
  5. [5]
    [PDF] CS153: Compilers Lecture 18: Compiling Objects ctd.
    Dynamic Dispatch Solution. •So we need some way at run time to figure out which code to invoke. •Solution: dispatch table. (aka virtual method table, vtable).
  6. [6]
    [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 == ...
  7. [7]
    Using Accessory Functions to Generalize Dynamic Dispatch in ...
    Existing single-dispatch languages restrict dynamic dispatch to the object receiving the message. Such languages exhibit a conflict between the goals of ...
  8. [8]
    [PDF] History of Lisp - John McCarthy
    Feb 12, 1979 · The implementation of LISP began in Fall 1958. The original idea was to produce a compiler, but this was considered a major undertaking, and we ...
  9. [9]
    [PDF] SIMULA Session - Department of Computer Science
    "Default" actuals for virtual procedures, provided at an application ... We are not presenting the true history of SIMULA 67 unless we tell this second.
  10. [10]
    The Early History Of Smalltalk
    Early Smalltalk was the first complete realization of these new points of view as parented by its many predecessors in hardware, language and user interface ...
  11. [11]
    [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].
  12. [12]
    [PDF] Oral History of James Gosling, part 2 of 2
    Apr 22, 2019 · So it's interesting that Mesa and then Java both had static typing but dynamic binding. I think that's a very interesting combination. Gosling ...
  13. [13]
    [PDF] Evolution of Common Lisp Object System
    Feb 1, 2023 · Lisp language was invented/discovered by John McCarthy in 1958, and in this regard, it is one of the oldest programming languages, the object ...
  14. [14]
    [PDF] Julia: A Fast Dynamic Language for Technical Computing
    Sep 25, 2012 · In Julia, multiple dispatch is used to define arithmetic and type promotion behaviors at the library level rather than in the compiler. As a ...
  15. [15]
    Announcing Rust 1.0 Alpha - Rust Blog
    Jan 9, 2015 · Dynamically-sized types (DSTs): Types whose size is only known at runtime (such as array slices and trait objects) are now largely integrated ...
  16. [16]
    [PDF] CSC 347 - Concepts of Programming Languages
    . dynamic dispatch. Static dispatch: select method based on type of reference. Dynamic dispatch: select method based on type of object. Inheritance vs ...
  17. [17]
    [PDF] Multiple Dispatch in Practice - Alex Potanin
    Abstract. Multiple dispatch uses the run time types of more than one argument to a method call to determine which method body.
  18. [18]
    Methods - Julia Documentation
    Using all of a function's arguments to choose which method should be invoked, rather than just the first, is known as multiple dispatch. Multiple dispatch ...Defining Methods · Parametric Methods · Design Patterns with...
  19. [19]
    The Common Lisp Cookbook – Fundamentals of CLOS
    it supports multiple dispatch and multiple inheritance,; it is different from most object systems in that class and method definitions are not tied together ...Classes and instances · Classes of traditional lisp types · See also · Methods
  20. [20]
  21. [21]
  22. [22]
  23. [23]
  24. [24]
  25. [25]
  26. [26]
    Itanium C++ ABI
    A virtual table consists of a sequence of offsets, data pointers, and function pointers, as well as structures composed of such items. We will describe below ...Chapter 1: Introduction · Chapter 2: Data Layout · Chapter 3: Code Emission and...
  27. [27]
    VirtualCalls - HotSpot - OpenJDK Wiki
    Apr 12, 2013 · A vtable is a display, associated with a class, of actual target methods for every virtual method which the class responds to, or any of its superclasses ...
  28. [28]
  29. [29]
  30. [30]
    Objects, Classes, and Messaging - Apple Developer
    Apr 23, 2013 · Objective-C takes dynamic binding one step further and allows even the message that's sent (the method selector) to be a variable determined ...
  31. [31]
    Descriptor Guide — Python 3.14.0 documentation
    Descriptors are objects that define __get__(), __set__(), or __delete__() methods, customizing attribute lookup, storage, and deletion. They are invoked by the ...Descriptor Guide · Technical Tutorial · Pure Python Equivalents
  32. [32]
  33. [33]
    Inheritance — <code>virtual</code> functions, C++ FAQ
    The v-table itself has pointers to each of the virtual functions in the class. For example, the Circle v-table would have three pointers: a pointer to Circle:: ...
  34. [34]
    Multiple and Virtual Inheritance, C++ FAQ - Standard C++
    What does it mean to “delegate to a sister class” via virtual inheritance? What special considerations do I need to know about when I use virtual inheritance?
  35. [35]
  36. [36]
  37. [37]
  38. [38]
  39. [39]
  40. [40]
  41. [41]
    Invoking Methods - The Java™ Tutorials
    Methods are invoked with java.lang.reflect.Method.invoke(). The first argument is the object instance on which this particular method is to be invoked.
  42. [42]
    The Python 2.3 Method Resolution Order — Python 3.14.0 ...
    A MRO is monotonic when the following is true: if C1 precedes C2 in the linearization of C, then C1 precedes C2 in the linearization of any subclass of C.The Python 2.3 Method... · Examples · Bad Method Resolution Orders
  43. [43]
  44. [44]
  45. [45]
  46. [46]
  47. [47]
    Dynamic Dispatch - Rust Training Slides by Ferrous Systems
    Internally, trait objects are a pair of pointers - one to a vtable and one the value itself. Note: The term vtable is short for virtual dispatch table, and it's ...
  48. [48]
    Traits: Defining Shared Behavior - The Rust Programming Language
    A trait defines the functionality a particular type has and can share with other types. We can use traits to define shared behavior in an abstract way.Missing: safety | Show results with:safety
  49. [49]
    0255-object-safety - The Rust RFC Book
    This RFC proposes enforcing object-safety when trait objects are created, rather than where methods on a trait object are called or where we attempt to match ...
  50. [50]
    from_raw_parts in std::ptr - Rust Documentation
    Forms a (possibly-wide) raw pointer from a data pointer and metadata. This function is safe but the returned pointer is not necessarily safe to dereference.
  51. [51]
    [PDF] Smalltalk-80: the language and its implementation - Free
    Smalltalk-80: the language and its implementation. 1. Smalltalk-80 (Computer system) I. Robson, David. II. Title. QA76.8.S635G64 1983. 001.64'2. 82-13741. ISBN ...
  52. [52]
    [PDF] Pharo By Example 5
    Sep 29, 2018 · Pharo by Example is a cen- tral book to welcome newcomers and it has ... In Pharo there are only dynamic message sends. For example, in ...
  53. [53]
    [PDF] Smalltalk's Influence on Modern Programming - Labouseur.com
    In Smalltalk, “everything is an object.” This is slightly different than the later implementations of Java, C++ and other successors, in that even primitives ( ...Missing: binding reflection
  54. [54]
    [PDF] The Common Lisp Object System: An Overview - Dreamsongs
    The Common Lisp Object System is an object-oriented system that is based on the concepts of generic functions, multiple inheritance, and method combination.<|separator|>
  55. [55]
    The Art of the Metaobject Protocol - MIT Press
    The CLOS metaobject protocol is an elegant, high-performance extension to the CommonLisp Object System. The authors, who developed the metaobject protocol and ...
  56. [56]
    16. Object Reorientation: Generic Functions - gigamonkeys
    The features CLOS contributed to Common Lisp range from those that can hardly be avoided to relatively esoteric manifestations of Lisp's language-as-language- ...Missing: history | Show results with:history
  57. [57]
    7.7 CLOS extensions - LispWorks
    The macro compile-all-dispatch-code compiles dispatch code and makes it available at run-time to generic functions used by an application that has no compiler.Missing: integration | Show results with:integration
  58. [58]
    If CLOS had a compile-time dispatch, what would happen to this ...
    Mar 6, 2022 · The answer is: you are modifying a system while it is running. If CLOS objects weren't re-definable at run-time, this would simply not work, or ...Does this CLOS code result in a runtime or a compile time error in ...Common lisp CLOS dispatch - Stack OverflowMore results from stackoverflow.com
  59. [59]
    None
    ### Summary of Runtime Overhead of Dynamic Dispatch
  60. [60]
    [PDF] Optimizing Dynamically-Dispatched Multimethod Calls with Compile ...
    Dylan is an object-oriented language with a combination of features that present both difficulties and opportunities for efficient compilation.
  61. [61]
    Garbage collection and efficiency in dynamic metacircular runtimes
    In dynamic object-oriented languages, low-level mechanisms such as just-in-time compilation, object allocation, garbage collection (GC) and method dispatch ...
  62. [62]
    Devirtualization in LLVM - ACM Digital Library
    This paper presents a novel way of handling C++ devirtualization in LLVM by unifying virtual table loads across calls using different SSA values to represent ...
  63. [63]
    PerformanceTechniques - HotSpot - OpenJDK Wiki
    May 25, 2013 · Performance techniques used in the Hotspot JVM · Constants · Low-level safety checks · Loops · Profiling · Deoptimization · Methods · Intrinsics.
  64. [64]
    [PDF] A Study of Devirtualization Techniques for a Java™ Just-In-Time ...
    In this paper, we evaluate the effectiveness of several devirtualization techniques to maximize the efficiency of compiler op- timizations. They are direct ...
  65. [65]
    [PDF] Reusable Inline Caching for JavaScript Performance
    In particular,. Google's V8 JavaScript engine provides APIs to enable host systems (e.g., web browsers) to cache the bytecode result for frequently executed ...
  66. [66]
    [PDF] Optimizing Lua using run-time type specialization
    One possible solution to this problem is a variable-sized byte- code, where we can have as many opcode bits as we need and fetch additional operands on demand.
  67. [67]
    Performance Improvements in .NET 10 - Microsoft Developer Blogs
    Sep 10, 2025 · Dynamic PGO triggers a form of cloning, as part of the guarded devirtualization (GDV) mentioned earlier: if the instrumentation data reveals ...
  68. [68]
    Rustc Optimization: Whole Program Devirtualization - Rust Internals
    Aug 22, 2020 · It looks exactly the same, and could benefit from speculative devirtualization, which LLVM calls Whole Program Devirtualization (WPD).Missing: guarded | Show results with:guarded
  69. [69]
    Unveiling the Profile Guided Optimization - arXiv
    Jul 22, 2025 · The fundamental trade-off in feedback optimization today lies in the choice of sampling technology: hardware sampling (e.g., Intel PEBS, AMD ...Missing: dispatch | Show results with:dispatch
  70. [70]
    (PDF) A study of devirtualization techniques for a Java Just-In-Time ...
    Aug 7, 2025 · Many devirtualization techniques have been proposed to reduce the runtime overhead of dynamic method calls for various object-oriented ...