Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

When a type implements a trait, we have the possibility to view this type as-is, or as the trait it implements. This property is called polymorphism. With polymorphism we have access to the different views of an object. Each view restricts the actions we can make with the object, letting us access only a subset of its methods.

To illustrate polymorphism, let us redefine our Surface trait, the Rectangle structure and its implementation of the trait:

With polymorphism, our Rectangle can be seen as a Surface instead of being seen as a Rectangle. Two kind of polymorphism are possible:

20.2.1Compile-time polymorphism

In compile-time polymorphism, a function accepting a trait as a parameter will be generated multiple times by the compiler: once for each concrete type used with the function.

To write a function that uses compile-time polymorphism to accept a trait as input argument, we use the impl keyword. Here is a function that prints the area of an object that implements the Surface trait:

Let us define a Rectangle instance and use it with this function:

Area is 37.260000000000005.

When calling the function, we pass a reference to our object. The compiler creates a version of the print_surface() that accepts a Rectangle instance but only accesses it through its Surface trait.

The interest of having defined the function with a trait as argument, is that we can use it on any other type that implements that trait. For instance, we can define the following Circle structure:

And implement the Surface trait for it:

We can now define a Circle instance and call the print_surface() function on it:

Area is 141.02609421964584.

As for the call with the rect instance, the compiler creates a specific version of print_surface() that accepts a Circle instance.

20.2.2Runtime polymorphism

In runtime polymorphism, a function accepting a trait as parameter will be generated just once, as-is, and will receive the parameter as a dynamic object (i.e.: using a vtable).

To write a function that uses runtime polymorphism to accept a trait as input argument, we use the dyn keyword. dyn stands for dynamic.

The dyn keyword is used to construct a fixed size type composed of a data pointer and a vtable pointer. The data pointer points to the data fields of the instance, while the vtable pointer points to a table containing the list of methods of the trait. vtable stands for virtual method table. The methods defined by a trait are seen as virtual methods when used dynamically.

Here is an illustration of the &dyn Surface for a Circle instance:

       &dyn Surface
┌────────────┬──────────────┐
│data pointer│vtable pointer│  --> fixed size
└────────────┴──────────────┘
      │               │
      ▼               ▼
  [Circle data]   [Circle's vtable]
                    area  -> Circle::area()

Here is the implementation of the print_surface() function when using a dynamic object as argument:

The usage of the function is the same than in compile-time polymorphism:

Area is 141.02609421964584.

20.2.3Runtime polymorphism in collections

Using runtime polymorphism, we can put objects that share a common trait together into a same collection.

Here is a vector of objects that implement the Surface trait:

We can put in it our two objects rect and circ:

When iterating over objects contained inside the vector, we can call any method of the trait:

Area is 37.260000000000005.
Area is 141.02609421964584.
()