Sobrecarga de Operadores, to_string y Encadenamiento
En esta página
Sobrecarga por convención de nombre
El VM de Zolo resuelve los operadores buscando métodos con nombres canónicos en
el tipo del operando izquierdo: add para +, neg para - unario, eq para
==. Como el verificador de tipos estático es conservador, usa la forma directa
a.add(b) para pasar zolo check:
Vec2 con add, neg, eq y to_string — llamados directamente por nombre de método.
// Feature: Operator overloading via special methods
// Syntax: define `add`, `eq`, `neg`, `to_string` etc. in the `impl`
// When to use: make custom types usable with `+`, `-`, `==` at
// runtime. The VM resolves the operator by looking up the method
// with the canonical name on the type of the left operand.
//
// Note: the conservative static checker may reject `a + b` for
// structs, so the DIRECT form is `a.add(b)`. The runtime effect is
// the same. We use the direct form here to satisfy the checker.
struct Vec2 {
x: float,
y: float,
}
impl Vec2 {
fn new(x: float, y: float) -> Vec2 {
return Vec2 { x: x, y: y }
}
// `+` convention.
fn add(self, other: Vec2) -> Vec2 {
return Vec2 { x: self.x + other.x, y: self.y + other.y }
}
// Unary `-` convention.
fn neg(self) -> Vec2 {
return Vec2 { x: -self.x, y: -self.y }
}
// `==` convention.
fn eq(self, other: Vec2) -> bool {
return self.x == other.x && self.y == other.y
}
// String conversion used by `print` / interpolation.
fn to_string(self) -> str {
return "Vec2({self.x}, {self.y})"
}
}
let a = Vec2::new(3.0, 4.0)
let b = Vec2::new(1.0, 2.0)
// Direct form — always works in the checker.
let c = a.add(b)
print(c.to_string()) // Vec2(4, 6)
let n = a.neg()
print(n.to_string()) // Vec2(-3, -4)
print(a.eq(b)) // false
let d = Vec2::new(3.0, 4.0)
print(a.eq(d)) // true
Sobrecarga con @op / @operator
La forma con decorador vincula explícitamente un método a un símbolo de operador.
El verificador de tipos reconoce esta declaración, permitiendo escribir a + b
directamente en el código. El nombre del método queda libre — elige el que sea más
legible en el contexto:
@op("+"), @op("-"), @op("unary-") y @operator("==") — sintaxis natural de operador aceptada por el checker.
// Feature: Operator overloading via `@op` / `@operator` decorators
// Syntax: `@op("symbol") fn <any_name>(self, other) -> T`
// When to use: when you want the method name to read well in the
// rest of the code (e.g. `scale`, `dot`, `combine`) but still bind
// it to an operator. Unlike the name-convention form (see file 13),
// the decorator form is recognized by the static type checker, so
// you can write `a + b` directly without the `a.add(b)` workaround.
//
// Supported symbols: `+`, `-`, `*`, `/`, `%`, `==`, `<`, `<=`, `..`,
// and the unary forms `unary-`, `not`, `len`. The full table lives
// in `docs/08-decorators.md`.
struct Vec2 {
x: float,
y: float,
}
impl Vec2 {
fn new(x: float, y: float) -> Vec2 {
return Vec2 { x: x, y: y }
}
// Method name is free; the decorator does the wiring.
@op("+")
fn plus(self, other: Vec2) -> Vec2 {
return Vec2 { x: self.x + other.x, y: self.y + other.y }
}
@op("-")
fn minus(self, other: Vec2) -> Vec2 {
return Vec2 { x: self.x - other.x, y: self.y - other.y }
}
// Unary minus uses the `unary-` symbol to disambiguate from `-`.
@op("unary-")
fn invert(self) -> Vec2 {
return Vec2 { x: -self.x, y: -self.y }
}
// `@operator` is the long alias for `@op`.
@operator("==")
fn same(self, other: Vec2) -> bool {
return self.x == other.x && self.y == other.y
}
fn to_string(self) -> str {
return "Vec2({self.x}, {self.y})"
}
}
let a = Vec2::new(3.0, 4.0)
let b = Vec2::new(1.0, 2.0)
// Natural operator syntax — the static checker accepts it because
// the decorator makes the binding explicit at the declaration site.
let sum = a + b
print(sum.to_string()) // Vec2(4, 6)
let diff = a - b
print(diff.to_string()) // Vec2(2, 2)
let neg = -a
print(neg.to_string()) // Vec2(-3, -4)
print(a == b) // false
let a2 = Vec2::new(3.0, 4.0)
print(a == a2) // true
Representación textual con to_string
Define fn to_string(self) -> str en impl para controlar lo que print y la
interpolación "{obj}" muestran. La función builtin tostring(x) delega en
to_string cuando está presente, y devuelve la forma natural para primitivos:
User con to_string; tostring aplicado a int, float, bool, str, nil y array.
// Feature: `to_string` — controls textual representation
// Syntax: implement `fn to_string(self) -> str` in the `impl`
// When to use: so that `print(obj)` and interpolation `"{obj}"`
// return a readable string instead of the default dump.
struct User {
name: str,
age: int,
}
impl User {
fn to_string(self) -> str {
return "User({self.name}, {self.age})"
}
}
let u = User { name: "Alice", age: 30 }
print(u.to_string()) // User(Alice, 30)
print("user: {u.to_string()}") // user: User(Alice, 30)
// `tostring(x)` — builtin function that delegates to `to_string`
// when present; for primitives returns the natural form.
print(tostring(42)) // 42
print(tostring(3.14)) // 3.14
print(tostring(true)) // true
print(tostring("hi")) // hi
print(tostring(nil)) // nil
print(tostring([1, 2, 3])) // [1, 2, 3]
Encadenamiento de métodos
Cuando cada método devuelve self (o una nueva instancia del mismo tipo), las
llamadas pueden encadenarse en secuencia. Este patrón, conocido como builder,
es idiomático para construir valores paso a paso:
Builder::new().word("Hello").punct(", ").word("world").punct("!").build() — cadena fluente.
// Feature: Method chaining — fluent calls
// Syntax: each method returns `self` (or a new `Type`)
// When to use: builder pattern, successive transformations, DSLs.
use std::Array
struct Builder {
parts: [str],
}
impl Builder {
fn new() -> Builder {
return Builder { parts: [] }
}
// Mutation + returning self => allows chaining.
fn word(self, w: str) -> Builder {
self.parts.push(w)
return self
}
fn punct(self, p: str) -> Builder {
self.parts.push(p)
return self
}
fn build(self) -> str {
var out = ""
for p in self.parts {
out = out + p
}
return out
}
}
let s = Builder::new().word("Hello").punct(", ").word("world").punct("!").build()
print(s) // Hello, world!
// expected:
// Hello, world!
Desafío
Extiende Builder con un método upper(self) -> Builder que convierta la última
palabra añadida a mayúsculas antes de devolver self.
Consulta también