5.1.39. Dynamic Type Checking
This tutorial covers runtime type checking and safe downcasting for class
hierarchies using the daslib/dynamic_cast_rtti module. This requires
options rtti to enable runtime type information.
options rtti
require daslib/dynamic_cast_rtti
5.1.39.1. Class hierarchy setup
All examples use a small shape hierarchy:
class Shape {
name : string
def abstract const area() : float
}
class Circle : Shape {
radius : float
def override const area() : float {
return 3.14159265 * radius * radius
}
}
class Rectangle : Shape {
width : float
height : float
def override const area() : float {
return width * height
}
}
class Square : Rectangle {
def Square(s : float) {
width = s
height = s
}
}
Note the use of def abstract const and def override const — the
const modifier makes self immutable, allowing the method to be called
through const pointers.
5.1.39.2. is_instance_of
is_instance_of checks whether a class pointer is an instance of a given
type at runtime, walking the RTTI chain. Works with both exact and derived
types:
var c = new Circle(name = "circle", radius = 5.0)
let s_c : Shape? = c
print("{is_instance_of(s_c, type<Circle>)}\n") // true
print("{is_instance_of(s_c, type<Rectangle>)}\n") // false
// Derived types match base checks
var sq = new Square(name = "square")
let s_sq : Shape? = sq
print("{is_instance_of(s_sq, type<Rectangle>)}\n") // true
print("{is_instance_of(s_sq, type<Shape>)}\n") // true
// Null is never an instance of anything
var np : Shape? = null
print("{is_instance_of(np, type<Shape>)}\n") // false
5.1.39.3. dynamic_type_cast — safe downcast
dynamic_type_cast returns a typed pointer if the cast succeeds, or
null if it fails. This is the “safe” downcast:
def describe_shape(s : Shape?) {
let circle = dynamic_type_cast(s, type<Circle>)
if (circle != null) {
print("Circle: radius={circle.radius}, area={s->area()}\n")
return
}
let rect = dynamic_type_cast(s, type<Rectangle>)
if (rect != null) {
print("Rectangle: {rect.width}x{rect.height}\n")
return
}
print("Unknown shape: area={s->area()}\n")
}
5.1.39.4. force_dynamic_type_cast
force_dynamic_type_cast panics if the cast fails. Use when you are
certain of the type and want a clear error if your assumption is wrong:
var c = new Circle(name = "c", radius = 7.0)
let s : Shape? = c
let fc = force_dynamic_type_cast(s, type<Circle>)
print("radius={fc.radius}\n") // 7
// This would panic:
// let fr = force_dynamic_type_cast(s, type<Rectangle>)
5.1.39.5. is / as / ?as syntax sugar
After requiring daslib/dynamic_cast_rtti, you get syntactic sugar
that maps to the functions above:
Syntax |
Equivalent |
|---|---|
|
|
|
|
|
|
let s_c : Shape? = c
// is — type check
print("{s_c is Circle}\n") // true
print("{s_c is Rectangle}\n") // false
// as — force cast (panics on failure)
let circle = s_c as Circle
print("radius={circle.radius}\n")
// ?as — safe cast (null on failure)
let maybe_rect = s_c ?as Rectangle
if (maybe_rect == null) {
print("not a rectangle\n")
}
5.1.39.6. Practical: polymorphic processing
Combine is and as to process a heterogeneous collection of shapes:
var shapes : array<Shape?>
shapes |> push(new Circle(name = "sun", radius = 10.0))
shapes |> push(new Rectangle(name = "wall", width = 8.0, height = 3.0))
var total_area = 0.0
for (s in shapes) {
if (s is Circle) {
let c = s as Circle
print("{c.name}: circle r={c.radius}\n")
} elif (s is Square) {
let sq = s as Square
print("{sq.name}: square side={sq.width}\n")
} elif (s is Rectangle) {
let r = s as Rectangle
print("{r.name}: rect {r.width}x{r.height}\n")
}
total_area += s->area()
}
print("total area: {total_area}\n")
Note
Check more-derived types first — Square must come before
Rectangle since Square extends Rectangle.
5.1.39.7. Summary
Function / Syntax |
Description |
|---|---|
|
|
|
Returns |
|
Returns |
|
Syntactic sugar for |
|
Syntactic sugar for force cast |
|
Syntactic sugar for safe cast |
See also
Classes — class inheritance and virtual methods.
Full source: tutorials/language/39_dynamic_type_checking.das
Previous tutorial: Random Numbers
Next tutorial: Coroutines