Many object-oriented languages, such as C++ and C#, include the concept of a struct that form the basis of objects. A struct is an example of a compound data type, a data type composed from other types. This allows us to represent data in more complex ways by combining multiple primitive data types into a new type. This too, is a form of encapsulation, as it allows us to collect several values into a single data structure. Consider the concept of a vector from mathematics - if we wanted to store three-dimensional vectors in a program, we could do so in several ways. Perhaps the easiest would be as an array or list:

double[] vector = {3.0, 4.0, 5.0};
vector: List[float] = [3.0, 4.0, 5.0]

However, other than the variable name, there is no indication to other programmers that this is intended to be a three-element vector. And, if we were to accept it in a function, say a dot product, we’d need to check that the length of both arrays or lists was exactly 3:

public double dotProduct(double[] a, double[] b){
    if(a.length != 3 || b.length != 3){
        throw new IllegalArgumentException();
    }
    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
}
def dot_product(a: List[float], b: List[float]) -> float:
    if len(a) != 3 or len(b) != 3:
        raise ValueError()
    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]

A struct provides a much cleaner option, by allowing us to define a type that is composed of exactly three integers. Java and Python don’t directly support structs, but we can use classes with just variables and a constructor to mimic a struct in those languages:

public class Vector3{
    public double x;
    public double y;
    public double z;
    
    public Vector3(double x, double y, double z){
        this.x = x;
        this.y = y;
        this.z = z;
    }
}
class Vector3:
    
    def __init__(self, x: float, y: float, z: float) -> None:
        self.x = x
        self.y = y
        self.z = z

Then, our dot product method can take two arguments of the Vector3 type:

public double dotProduct(Vector3 a, Vector3 b){
    return a.x * b.x + a.y * b.y + a.z * b.z;
}
def dot_product(a: Vector3, b: Vector3) -> float:
    return a.x * b.x + a.y * b.y + a.z * b.z

There is no longer any concern about having the wrong number of elements in our vectors - it will always be three. We also get the benefit of having unique names for these fields (in this case, x, y, and z).

Thus, a struct allows us to create structure to represent multiple values in one variable, encapsulating the related values into a single data structure. We can then use those data structures as new data types in our program. Variables, and compound data types, together represent the state of a program. We’ll examine this concept in detail next.