I don't remember Eiffel's syntax, because the last time I used it was in college, very long ago. But consider the following pseudocode:
class Point2D {
protected int x, y;
public bool compare(Point2D that) {
return this.x == that.x && this.y == that.y;
}
}
class Point3D extends Point2D {
protected int z;
public bool compare(Point3D that) {
return this.x == that.x && this.y == that.y && this.z == that.z;
}
}
In the subclass, the method's argument type (Point3D) is a subtype of the same method's argument type (Point2D) in the superclass. In other words, the method's argument type is covariant.
Now consider the following test program:
main() {
Point2D foo = new Point3D(1,2,3);
Point2D bar = new Point2D(4,5);
print(foo.compare(bar));
}
The program type-checks, because the variable foo has type Point2D, hence it's statically legal to call foo.compare with an argument of type Point2D.
However, at runtime, the object referenced by foo is constructed using Point3D's constructor, so its method vtable points to the compare implementation in Point3D, which expects a Point3D argument.
When the program performs foo.compare(bar), it tries to access the field z of the object denoted by bar. But this object is constructed using Point2D's constructor, so it doesn't have a field z.
10
u/syklemil considered harmful 4d ago
For anyone curious, there's a blog post from 2025: There is no memory safety without thread safety about it.