​

Toit for Python programmers

For readers comfortable writing Python functions and classes. Toit's indentation will look familiar, but calls, control flow, and values have different rules. This page highlights those differences. For the full language introduction, read Toit for programmers, which explains each feature directly and draws comparisons with C/C++, JavaScript, Python, and Java/Kotlin.

Indentation is familiar; calls are different

Start with a function and a call. Toit uses two spaces per indentation level, and the function name is followed directly by its parameters:

greet name:
  print "Hello $name"

main:
  greet "Ada"

This prints Hello Ada. Spaces separate call arguments; parentheses group expressions. For example, print (int.parse "42") passes the parsed integer to print.

For a zero-argument method, writing its name calls it: name.trim invokes trim. Reading a public field uses the same syntax.

Variables and names

Use := for a new variable and = to assign it again. Variables and functions use kebab-case, so reading-count is one identifier. Put spaces around subtraction, as in reading - count. Comments start with //.

main:
  reading-count := 2
  reading-count = 3
  print reading-count  // Prints 3.

Named arguments

Toit writes named arguments with -- in both the definition and the call. A default value applies when the caller omits the argument:

greet name --greeting="Hello":
  print "$greeting $name"

main:
  greet "Ada"
  greet "Grace" --greeting="Welcome"

This prints Hello Ada, then Welcome Grace. Positional parameters can also have defaults.

Test emptiness explicitly

Only false and null are falsy in Toit. Zero, empty strings, and empty collections are truthy. Port Python's if items: as if not items.is-empty: when the intent is to check for elements. null represents an absent value, like Python's None.

describe items:
  if items:
    print "The list exists"
  if items.is-empty:
    print "The list is empty"

main:
  describe []

Expected output:

The list exists
The list is empty

Integer division truncates toward zero

Toit has signed 64-bit integers, rather than Python's arbitrary-precision integers. Integer / truncates toward zero, while Python's // rounds down. A float operand gives a fractional result:

main:
  print (-5 / 2)   // Prints -2; Python's -5 // 2 is -3.
  print (5 / 2.0)  // Prints 2.5.

Iteration blocks can return from their function

Use items.do: |item| to iterate, and items.map: |item| to transform a list. The block's last expression is its result. A return inside a block leaves the surrounding function, making it useful for searches and early exits. A block cannot be stored for later execution; use a :: lambda for that.

first-long words:
  words.do: |word|
    if word.size > 4:
      return word
  return null

main:
  print (first-long ["cat", "horse", "rabbit"])

This prints horse. Returning from the enclosing function is called a non-local return. Use continue.do to skip an iteration without returning from the function. Read blocks and lambdas.

Declare fields in the class

Toit classes declare their fields. A method cannot add new attributes to an object, so every instance has the shape defined by its class. Methods do not declare a self parameter and can refer to fields directly:

class Person:
  name := ?

  constructor .name:

  greet:
    print "Hello $name"

main:
  person := Person "Ada"
  person.greet

This prints Hello Ada. := ? requires the constructor to initialize the field. The .name constructor parameter stores its argument directly in that field. Calling Person creates the object; greet is an ordinary method.

When keys must be added dynamically, use a map. {"name": "Ada"} creates a map; read the value with person["name"]. {:} is an empty map and {} an empty set. See classes.

Type annotations check values

Annotations use /, as in name/string, and -> for a return type. Unlike ordinary Python type hints, Toit annotations are enforced at runtime:

length text/string -> int:
  return text.size

main:
  print (length "Ada")  // Prints 3.
  value/any := 42
  error := catch: length value
  if error:
    print "Expected text"

The any annotation permits any kind of value in value. Passing it to length checks whether it is a string. The second call fails this check and prints Expected text. The analyzer can catch some mistakes before execution, but not every type error.

Types exclude null unless marked with ?: string? accepts a string or null. Use any to explicitly allow any value, and none for a function with no result. See types for casts and type tests.

Ordinary functions can wait

Toit tasks retain their call stacks while waiting for I/O. You do not need an async def/await chain to suspend an activity. task:: starts a task; sleep --ms=100 suspends the current one. Within a program, tasks share objects and cooperate, rather than executing as parallel threads.

A loop without yielding prevents other tasks from running. A waiting library call can let shared state change before it returns. Read tasks for synchronization and examples.

Exceptions and imports have their own syntax

Use throw to raise an exception, catch: to capture one, and try: with finally: for cleanup. Each file is a library; import .helpers imports a sibling file. Continue with exceptions, imports, and the package quick start.

For additional examples of call grouping, string interpolation, defaults, and block control flow, expand the example collections in Toit for programmers.