Skip to content

Object-oriented programming

Vow’s OOP surface is intentionally small: data + methods, interfaces, single inheritance, and composition. There is no garbage collector, no prototypes, and no multiple inheritance.

Hands-on path: Tutorial — OOP.

class and struct are the same layout. Methods take an explicit self:

class User {
name: String
age: int
fn greet(self) -> String {
return "hello " + self.name
}
}
fn main(caps: Caps) -> int {
var u = User { name: "Ada", age: 36 }
u.age = 37
print u.greet()
return u.age
}
  • Fields are mutable on var bindings (u.age = 37)
  • Methods use fn, not function
  • Literal syntax: User { name: "Ada", age: 36 }
  • Constructors: define fn new(...) -> Type, then new Type(...)
class Counter {
n: int
fn new(start: int) -> Counter {
return Counter { n: start }
}
}
fn main(caps: Caps) -> int {
var c = new Counter(0)
return c.n
}
class Timestamps {
created_at: int
updated_at: int
}
class User {
name: String
times: Timestamps
}

Has-a, not is-a — you always see where a field came from.

interface Shape {
fn area(self) -> int
fn describe(self) -> int {
return self.area()
}
}
struct Circle {
radius: int
fn area(self) -> int {
return 3 * self.radius * self.radius
}
}
fn report(s: Shape) -> int {
return s.describe()
}
  • Interface methods may include a default body
  • A parameter typed as Shape is static dispatch (monomorphized per call site)
  • See tests/lang/interface_basic.vow

When one list must hold different implementors:

// fragment-only
fn total_area(shapes: Array<dyn Shape>) -> int {
var sum = 0
for s in shapes {
sum = sum + s.area()
}
return sum
}
fn main(caps: Caps) -> int {
var shapes = [
Circle { radius: 2 } as dyn Shape,
Square { side: 3 } as dyn Shape
]
return total_area(shapes)
}

dyn makes the vtable cost visible in the type. See tests/lang/dyn_shapes.vow.

// fragment-only
open class Animal {
name: String
fn speak(self) -> String {
return "..."
}
}
class Dog extends Animal {
breed: String
override fn speak(self) -> String {
return "woof"
}
}
fn main(caps: Caps) -> int {
let d = Dog { name: "rex", breed: "lab" }
assert d.name == "rex"
assert d.breed == "lab"
assert d.speak() == "woof"
return 0
}
Rule Meaning
Closed by default Only open class may be subclassed
Single inheritance One extends parent — no diamond hierarchy
override mandatory Shadowing a parent method without override is a type error
Static dispatch Method resolution is compile-time today

See tests/lang/inheritance.vow.

Return self for fluent builders:

// fragment-only
class Response {
status: int
body: String
fn status(self, code: int) -> Response {
self.status = code
return self
}
fn json(self, payload: String) -> Response {
self.body = payload
return self
}
}
fn main(caps: Caps) -> int {
let r = Response { status: 200, body: "" }
let r2 = r.status(404).json("{\"error\":\"not found\"}")
return r2.status
}

See tests/lang/method_chain.vow. Overloads (response.send / json in vow-web-server) resolve at compile time by arity and types.

Need Prefer
Shared behavior interface (+ dyn if heterogeneous)
Shared fields with specialization open / extends / override
Shared fields without hierarchy Composition (nested struct)
Fluent builders Method chaining (return self)