Generics

Write functions that work across types and understand explicit type arguments with monomorphization.

1

Core concepts

type parameters, fn name[T], generic arrays, explicit type arguments

2

How the learner should approach this project

Do not paste the complete program immediately. Create the file, type the first step, run vpp check, and then run the program when a complete entry point exists. Read the explanation before looking at the snippet. After each step, predict what the new code should do. This turns the page into a lesson rather than a code dump.

3

Step 1: Identity function

T is a type parameter. The function accepts a value of any concrete type and returns that same type. The compiler can specialize the generic function for each concrete use.

After typing the snippet, identify the new names introduced by the step. Ask what each name represents, what type the compiler should infer or check, and what value should exist after the code runs.

main.vpp
fn id[T](x: T) -> T {
    return x
}
4

Step 2: Generic first element

The same type parameter can describe an array element and the return value. first therefore works for arrays of integers, strings, or other supported types.

Before moving forward, explain how this step connects to the previous one. In particular, identify which values cross a function boundary, which values change, and which values are guaranteed by the type system.

main.vpp
fn first[T](items: array[T]) -> T {
    return items[0]
}
5

Step 3: Instantiate generic functions

Explicit type arguments make the intended specialization visible. id[int] creates an integer use and id[string] creates a string use. first[string] accepts an array of strings.

Before moving forward, explain how this step connects to the previous one. In particular, identify which values cross a function boundary, which values change, and which values are guaranteed by the type system.

main.vpp
fn main() -> int {
    print(id[int](42))
    print(id[string]("v++"))
    let words = ["alpha", "beta", "gamma"]
    print(first[string](words))
    return 0
}
6

Complete source

main.vpp
fn id[T](x: T) -> T {
    return x
}
fn first[T](items: array[T]) -> T {
    return items[0]
}
fn main() -> int {
    print(id[int](42))
    print(id[string]("v++"))
    let words = ["alpha", "beta", "gamma"]
    print(first[string](words))
    return 0
}
7

Expected behavior

The complete program should produce the following output when run with the command shown below. Exact formatting should follow the current V++ runtime.

42
v++
alpha
8

Run the project

terminal
vpp run projects/12-generics/main.vpp
9

What the learner should understand after this project

Generics let one function describe a family of concrete functions. Monomorphization means concrete uses can be compiled with concrete types.

10

Common mistakes to teach

  1. Using a concrete type where T is required
  2. Forgetting explicit type arguments when the example requires them
  3. Passing an array whose element type does not match T
11

Practice extension

Create a generic last[T] function. Test it with an integer array and a string array.

A strong learner should be able to explain the program without looking at the code, rebuild the core idea from memory, and make the practice change without copying a solution.

12

Full program

main.vpp
fn id[T](x: T) -> T {
    return x
}
fn first[T](items: array[T]) -> T {
    return items[0]
}
fn main() -> int {
    print(id[int](42))
    print(id[string]("v++"))
    let words = ["alpha", "beta", "gamma"]
    print(first[string](words))
    return 0
}
vpp
$ ready. Click Test program.