3 ms·
When state is stored simply in variables, especially public fields in a structure, then any code can access them -- even if doing so will violate invariants tha
by jcrites 4y ago
When state is stored simply in variables, especially public fields in a structure, then any code can access them -- even if doing so will violate invariants that the type is expected to uphold.
For example, if I have a `String` whose byte sequences must be valid UTF8 (as in the case of Rust's `String` type), or if I have a `UnitVector3` (which is a `Vector3` that must be of unit length), then allowing direct manipulation of structure fields (X, Y, Z) creates the possibility of instances existing that break the invariant. In the case of Rust `String` that leads to memory unsafety.
Abstractions make it possible to enforce invariants using the type system. If `UnitVector3` provides functions such as a constructor that either (1) takes a valid unit vector as input, or else fails or (2) takes any vector as input, and changes its length to become a unit vector (etc.); and also all of the type's functions return `UnitVector3` (where appropriate, e.g. transformations such as rotation), then it is impossible for an invalid `UnitVector3` to exist!
To continue the example, if I add two vectors together, then their sum will be a `Vector3`, not a `UnitVector3` -- so that operation should have the appropriate return type (`Vector3`). Meanwhile rotating a `UnitVector3` will always return a `UnitVector3`. But translating a `UnitVector3` yields a `Vector3`. And so on. Any code written using these operations can determine from their return type what scenarios it needs to handle (is the return value guaranteed to be another unit vector, or could it be any vector?).
If any code can access and manipulate the X, Y, Z coordinates of `UnitVector3`, then any code that relies upon instances being actual unit vectors has the potential to misbehave. That code is correct -- it's the code that created an invalid `UnitVector3` that's wrong! But the error will show up somewhere else at runtime.
Encapsulating these fields within a type (that provides getter methods), and provides a constructor that validates the input (and fails on invalid), makes this entire class of error impossible.
It is still possible for code to attempt to construct an invalid `UnitVector3`, but the call to the constructor (which validates its input) will fail, thus causing the program to fail as soon as possible, and in the most relevant place!: In the code that's creating an invalid vector -- not some obscure other part of the program that is processing the invalid `UnitVector3`.
Using the type system to enforce soundness properties is impractical to achieve without encapsulation, and the ability to hide data and provide interfaces to it. Structures with public fields, where any code can modify their value, is very high risk by comparison.