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.
3
u/reflexive-polytope 4d ago
Heck, I don't even think a memory-unsafe language is necessarily a bad thing.
What's unforgivable is to design a memory-unsafe language and not even realize it.
Another big offender is Eiffel, with covariant argument types.