Toit for JavaScripters
For JavaScript and TypeScript programmers who already know functions, objects, and asynchronous code. This page highlights assumptions to revisit when moving to Toit. 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.
Each Toit example is a complete program you can run using the local setup.
Calling functions and methods
console.log(value) becomes print value: spaces separate the arguments.
When one call supplies an argument to another, group it with parentheses:
print (int.parse "42").
A zero-argument method is called by naming it. In this example, name.trim
calls trim and returns a string:
This prints Ada. Toit uses indentation to group statements, with two spaces
per level. Names for variables and functions use kebab-case.
Variables and final bindings
Use := to introduce a local variable and = to reassign it. Toit normally
uses mutable locals, even when a particular variable is only assigned once.
::= declares a final binding, like JavaScript's const: the binding cannot
be reassigned, but the object can still change. Prefer final bindings for fields
and globals when reassignment is unnecessary. A common field declaration is
name/string, which also makes the field final; we will use it in the class
example below.
Named arguments
Named arguments are part of a function's signature and its calls. They are
written with --; they are not properties of an options object:
greet name --greeting="Hello": print "$greeting $name" main: greet "Ada" greet "Grace" --greeting="Welcome"
This prints Hello Ada, then Welcome Grace. The default is used when the
argument is omitted. Positional parameters can have defaults too.
See syntax for more call examples.
Strings interpolate with $
Use "Hello $name" or "Hello $(name.trim)" in place of a template literal's
${name} expression. Toit uses ordinary double quotes for interpolated strings.
main: name := "Ada" print "Hello $name" // Prints Hello Ada. print "Name length: $name.size" // Prints Name length: 3.
Dot access and indexing are included in the interpolation. Parentheses delimit
it: "$(name).size" produces Ada.size. See the
interpolation examples
for an expandable collection covering boundaries, expressions, and formatting.
Blocks are not arrow functions
The : in values.do: |value| introduces a block. Like a callback passed to
forEach, it runs for each element, but its control flow is different:
return leaves the enclosing function, not just the iteration.
The last expression gives a block its value. Use continue.do to skip to the
next iteration. Use :: for a lambda that can be stored or returned. A lambda
uses its final expression as its result and cannot contain an explicit return.
This distinction lets library iteration behave like a loop while supporting
callbacks with a longer lifetime.
find-long-name names:
names.do: |name|
if name.size > 4:
return name
return null
main:
print (find-long-name ["Ada", "Grace", "Linus"])This prints Grace. This exit from the surrounding function is called a
non-local return. A return inside a JavaScript forEach callback would
only return from that callback. Here it ends find-long-name.
See blocks and lambdas for parameters and lifetime rules.
Waiting does not require async functions
In JavaScript, async functions return promises,
and await suspends their execution until a promise settles. In Toit, ordinary
functions can wait. The task keeps its call stack while another task runs;
callers do not need an async marker or an await expression.
Start a concurrent activity with task::. Within one program, tasks share
objects and run cooperatively. They do not run in parallel. A busy loop without
a yielding operation prevents other tasks from progressing.
announce-later message/string -> none: sleep --ms=10 print message main: task:: announce-later "Background done" print "Main continues"
announce-later waits without any special function declaration. The main task
can continue while the background task is asleep.
The tradeoff is that yield points are not marked at each call site. A library call that waits can allow another task to modify shared objects. Check APIs and use synchronization when an operation must remain consistent across waits. JavaScript's async/await also supports sequential-looking loops; the difference here is how waiting propagates through ordinary calls.
Zero and empty strings are truthy
JavaScript treats zero and the empty string as falsy; see
MDN's truthiness rules.
Toit treats only false and null as falsy. There is no separate undefined
value. and, or, and not replace &&, ||, and !.
This affects defaults: value or fallback preserves 0 and "", but replaces
false. Check value == null when a boolean false must also be preserved.
Check .size == 0 when you mean an empty collection or string.
Expected output:
Numbers distinguish integers from floats
Toit has signed 64-bit int values and floating-point float values.
5 / 2 is 2, and 5 / 2.0 is 2.5. When translating an average or a ratio
from JavaScript, ensure a floating-point operand is present if you need the
fraction. Convert text explicitly with int.parse or float.parse and use
string interpolation to format a value as text.
main: total := 5 count := 2 print (total / count) print (total.to-float / count) print (int.parse "42")
Expected output:
See numbers and conversions.
Choose a class or a map
Toit objects have fields declared in their classes. You cannot build their
shape by assigning arbitrary new properties. Use a class for a known structure
and a map for dynamic keys. {"name": "Ada"} is a map; access it as
person["name"], not person.name. {:} is an empty map; {} is an empty set.
Create an instance by calling its class, without new. A constructor parameter
such as .name initializes the corresponding field. Within methods, this
refers to the receiver. Unlike JavaScript, fields and methods are already in
scope inside a method: write name or greet without this..
class Person:
name/string
constructor .name:
greet -> none:
print "Hello $name"
main:
person := Person "Ada"
person.greet
labels := {"name": person.name}
print labels["name"]This prints Hello Ada, followed by Ada.
Types are checked at runtime
Toit lets you choose where to add type annotations. A parameter uses /Type,
and a return type uses -> Type. Here both arguments and the result must be
integers:
add a/int b/int -> int:
return a + b
main:
print (add 2 3) // Prints 5.
value/any := "3"
error := catch: add 2 value
if error:
print "Expected integers"The any annotation allows value to hold any kind of value. Passing it to
add checks whether it is an integer. Unlike TypeScript's erased annotations,
Toit annotations check actual values when the program runs. The second call fails its check and prints Expected
integers. The analyzer catches some mistakes before execution, but not every
type error. Omitting an annotation allows dynamic values; any explicitly
accepts any value, including null.
Nullable values
Types exclude null by default. Add ? to allow it: string? means a string
or null. This is useful for a value that may be absent:
This prints Hello Ada, then Hello World. none marks a function with no
result. A nullable value can be passed to a non-nullable parameter, but its
runtime value must satisfy that parameter's check; see
nullable assignments.
Interfaces are explicit
TypeScript can accept an object because its shape matches an interface.
In Toit, the class must declare implements as well as provide the methods:
interface Named: name -> string class Person implements Named: name/string constructor .name: main: person/Named := Person "Ada" print person.name
The public field supplies the name getter required by the interface. Use
value is Type to test a type, or value as Type for a checked cast. A cast
does not convert text to a number; use int.parse for that.
See types and
classes.
Catching errors returns a value
catch: runs a block and returns the thrown value or null if nothing was
thrown. Use try: with finally: for cleanup. This is different syntax from
JavaScript's try/catch/finally statement.
This prints Please enter an integer.
Exceptions are values; strings are common. For your own error protocols, throw
a truthy value so the usual if error check detects it. Read
exception handling for cleanup and error propagation.
Modules and packages are resolved before running
Each file is a library. import .helpers imports a neighboring helpers.toit;
SDK imports use names such as import math. Package dependencies are declared
in package.yaml and resolved versions live in package.lock.
JavaScript packages cannot be imported as Toit libraries. Continue with imports and the package quick start to structure a project and add Toit dependencies.