Setu 2026.11.9
static
YoSTEALTH/Setu
Edit

5. Classes#

/// A 2D vector with integer coordinates: comparisons, hashing, numbers, pickling.
pub const Vec = struct {
    x: i64 = 0, // instance data: every field has a default
    y: i64 = 0,

    pub const basetype = true; // Python may subclass it
    /// How many coordinates a `Vec` has.
    pub const DIMS = 2; // UPPER_CASE: a class attribute

    /// Sets the coordinates.
    pub fn __init__(self: *Vec, a: struct { x: i64 = 0, y: i64 = 0 }) void {
        self.x = a.x;
        self.y = a.y;
    }

    /// `Vec(x, y)`.
    pub fn __repr__(self: *const Vec) Error!*Object {
        var buf: [64]u8 = undefined; // fits 2 x 20 digits
        const text = std.mem.print(&buf, "Vec({d}, {d})", .{ self.x, self.y }) catch unreachable;
        return py.new(text);
    }

    /// The squared length.
    pub fn norm2(self: *const Vec) i64 {
        return self.x * self.x + self.y * self.y;
    }

    /// A new vector of the class `cls` (a subclass too).
    pub fn from_pair(cls: *c.TypeObject, x: i64, y: i64) Error!*Object {
        const obj = c.PyObject_CallNoArgs(@ptrCast(cls)) orelse return error.PythonError;
        VecType.data(obj).* = .{ .x = x, .y = y };
        return obj;
    }

    /// The dot product of two vectors.
    pub fn dot(a: *const Vec, b: *const Vec) i64 {
        return a.x * b.x + a.y * b.y;
    }
};
pub const VecType = setu.Class(Vec, @embedFile("class.zig")); // declare once, use this name

/// A `Vec` with a label: a Setu subclass, pickled with its base's fields.
pub const Labeled = struct {
    label: ?*Object = null, // owned reference: traversed, cleared, dropped by Setu

    pub const base = VecType;

    /// Sets the label (the coordinates stay 0).
    pub fn __init__(self: *Labeled, label: ?*Object) void {
        if (label) |l| py.incref(l);
        py.decref(self.label);
        self.label = label;
    }

    /// The label.
    pub fn get_label(self: *const Labeled) *Object {
        const l = self.label orelse py.none();
        py.incref(l);
        return l;
    }

    /// Sets the label; `None` is refused (a plain Zig error: `RuntimeError: NoLabel`).
    ///
    /// Type
    ///     value: object
    pub fn set_label(self: *Labeled, value: *Object) error{NoLabel}!void {
        if (value == py.none()) return error.NoLabel;
        py.incref(value);
        py.decref(self.label);
        self.label = value;
    }
};
pub const LabeledType = setu.Class(Labeled, @embedFile("class.zig"));

(Cut from test/class.zig, which gives Vec more hooks.) Stub: @disjoint_base\nclass Vec: with DIMS: ClassVar[int], def __init__(self, x: int = ..., y: int = ...) -> None:, def norm2(self) -> int:, @classmethod def from_pair(cls, x: int, y: int) -> Any:, @staticmethod def dot(a: Vec, b: Vec) -> int:; @final\nclass Labeled(Vec): with the property label. In Python:

>>> from pkg import Labeled, Vec
>>> v = Vec(3, y=4)  # __init__'s argument struct: positional or keyword
>>> v, v.norm2(), Vec.DIMS
(Vec(3, 4), 25, 2)
>>> Vec(x='a')
Traceback (most recent call last):
  ...
TypeError: 'str' object cannot be interpreted as an integer
>>> Vec.from_pair(1, 2), Vec.dot(Vec(1, 2), Vec(3, 4))  # a class method, a static method
(Vec(1, 2), 11)
>>> class Vec3(Vec):  # basetype
...     pass
>>> type(Vec3.from_pair(1, 2)).__name__
'Vec3'
>>> l = Labeled('home')
>>> l.label, isinstance(l, Vec)
('home', True)
>>> l.label = 'work'  # set_label
>>> l.label
'work'
>>> l.label = None
Traceback (most recent call last):
  ...
RuntimeError: NoLabel

Settings (pub const in the struct): name ("pkg.Name"; default <module>.<struct name>), base (another Class, created first), basetype, instantiable = false (only Zig creates instances, with new), getset / slots (extra c.GetSetDefs / c.Slots, with stub lines for them), weakref, dict (a __dict__), pickle (generated __getstate__ / __setstate__ over ints, floats, bools and ?*Objects of the type and its bases), plain_methods (.{"get_nowait"}: methods named like accessors), type_params ("T": Name[int] works, the stub says class Name[T]:), hidden (created, not added to the module, Any in the stub), and UPPER_CASE consts as class attributes.

Methods: a lowercase pub fn whose first parameter is named self (an instance method), cls: *c.TypeObject (a class method, @classmethod), anything else (a static method, @staticmethod). self is *T, *const T or *Object. get_x / set_x(self, value: U) are the property x. Parameters and results as in section 3; a py.Rest right after self is *args.

What the Class gives Zig: VecType.tp (the type object, after creation), VecType.data(obj) (the *Vec of an instance or subclass instance), VecType.object(d) (the object back from its data), VecType.check(obj), try VecType.new(.{ .x = 1 }) (a new instance, also of a type Python can not instantiate), setu.Instance(Vec).from(obj) (*Vec or TypeError).

Hooks, by name (the full list is at the top of src/class.zig; R is the hook's Zig result, Error!R is fine everywhere; a hook parameter is converted like a method argument, *Object keeps it raw, and a Path / buffer / Sequence parameter is a compile error since nothing would release it). Below: the hook (its slot): behaviour.

Comparisons: != is not __eq__ without __ne__; __eq__ without __hash__ makes the type unhashable, as in Python. Number operators: __r<op>__ runs when the instance is on the right, __i<op>__ in place (a void result returns self); pow with a modulus raises TypeError. A __poll__'s sent is null on the first send, then what the loop sent in. The reflected and in-place names in full, for searching: __radd__, __rsub__, __rmul__, __rtruediv__, __rfloordiv__, __rmod__, __rdivmod__, __rpow__, __rlshift__, __rrshift__, __rand__, __ror__, __rxor__, __rmatmul__; __iadd__, __isub__, __imul__, __itruediv__, __ifloordiv__, __imod__, __ipow__, __ilshift__, __irshift__, __iand__, __ior__, __ixor__, __imatmul__ (__idivmod__ is a compile error: Python has no in-place divmod).

A __send__ / __poll__ type may not also declare __iter__ / __next__ (generated); __poll__ and __send__ exclude each other. After .ready or an error, awaiting a __poll__ object again raises RuntimeError.

Generated by tool/setu_doc.py from the docstrings of Setu 2026.11.9.