Threadon / Classes

Classes

objects with fields and methods

Classes bundle fields together like a struct, and add methods that operate on them. There are two kinds:

Declaring a class

Write class, a name, a colon, then the fields. Fields are a name, a colon, and a type, one per line. Classes can only be declared at the top level of a file.

class Point:
    x: Int32
    y: Int32

Methods live in the same block. A method is a def whose first parameter is self, written with its own type:

class Point:
    x: Int32
    y: Int32

    def length(self: Point) -> Int32
        return self.x * self.x + self.y * self.y

Constructing instances

Create an instance by calling the class name with the __init__ argument list. __init__ sets the fields; it has no return type and may not return anything.

class Point:
    x: Int32
    y: Int32

    def __init__(self: Point, x: Int32, y: Int32)
        self.x = x
        self.y = y

def main() -> Int32
    p: Point = Point(3, 4)
    print(p.length())
    return 0
25
Set every field in __init__. A field you leave untouched holds the zero value of its type (0, 0.0, false), just like a missing struct field. There is no warning.

A class without a __init__ is constructed by calling it with no arguments: e: Empty = Empty().

Reading and writing fields, calling methods

Fields are read and written with a dot, like structs. Field writes with plain = apply to the variable, and direct changes stick.

def main() -> Int32
    p: Point = Point(3, 4)
    print(p.x)
    p.x = 10
    print(p.x + p.y)
    return 0
3 14

Call a method with a dot and parentheses; self is filled in automatically. Methods that return NoneType must end in a plain return. Inside a method, other methods are called through self.

def scale(self: Point, k: Int32) -> NoneType
    self.x = self.x * k
    self.y = self.y * k
    return

Classes have value semantics

An instance behaves like a struct value: passing it to a function or a method copies it. Changing a field of the copy does not touch the original.

class Counter:
    n: Int32
    def __init__(self: Counter, start: Int32)
        self.n = start

def bump(c: Counter) -> NoneType
    c.n = c.n + 1
    return

def main() -> Int32
    c: Counter = Counter(10)
    bump(c)
    print(c.n)
    return 0
10

bump changed its own copy, so the original still holds 10. A direct write to the variable’s field (c.n = 99, like in Using) does persist. Foreign classes are different — see below.

Inheritance

A class can name a single base class in parentheses. The subclass inherits the base’s fields, constructor, and methods, and can add new methods or override inherited ones.

class Base:
    a: Int32
    def __init__(self: Base, a: Int32)
        self.a = a
    def v(self: Base) -> Int32
        return self.a

class Double(Base):
    def v(self: Double) -> Int32
        return self.a * 10

def main() -> Int32
    d: Double = Double(5)
    print(d.v())
    return 0
50

Here Double reuses Base’s constructor and overrides v. A subclass inherits its base’s fields (set by the inherited constructor) and can declare extra fields of its own. The inherited constructor only writes the base fields, so a subclass’s own fields hold zero until you assign them — set them right after construction, as in Using.

Classes from foreign modules

A module written in C, C++, Rust, or Python can export classes through its manifest. They are used the same way: construct with the class name, then call methods.

from torch import Tensor

t: Tensor = Tensor([1.0, 2.0, 3.0, 4.0])
print(t.sum())
print(t.max())
10.000000 4.000000

Behind the scenes the value holds one hidden char* __handle pointer to the foreign object, so copies of a foreign instance do share the same object — hand the same Tensor around and it is the same tensor. The constructor and method signatures come from the manifest: class Tensor init List[Float64], class Tensor method sum -> Float64. Constructor and method arguments and return values can use scalar types, List[...], and Dict[...]; a class instance itself cannot be passed to or returned from a method yet.

The details of writing the manifest and the glue code for each backend are on the Imports page.