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:
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 //.
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:
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:
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.greetThis 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.