The Basics

There is something appealing to it, isn't it?

Unlike TypeScript, which targets a JavaScript environment with all of its dynamic features, AssemblyScript targets WebAssembly, and intentionally avoids the dynamicness of JavaScript where it cannot be compiled ahead of time efficiently.

The first thing one is going to notice is that AssemblyScript's type system differs from TypeScript's in that it uses WebAssembly's more specific integer and floating point types, with number merely an alias of f64. These types are enough to implement other numerical types and higher level structures like strings and arrays as provided by the standard library, yet the relevant specifications (like reference types and GC) to describe higher level structures to JavaScript are not yet available.

This means that objects cannot yet flow in and out of WebAssembly natively, making it necessary to read/write them from/to memory. To make this process more convenient, the loader provides the utility necessary to translate between the WebAssembly and the JavaScript world.

It must also be noted that WebAssembly has no immediate access to the DOM or other JavaScript APIs currently, sometimes making it necessary to create some custom glue code by means of importing and exporting functionality from/to JavaScript.

Combined, this makes it unlikely that existing TypeScript code can be compiled to WebAssembly without modifications, yet likely that already reasonably strict TypeScript code can be made compatible with the AssemblyScript compiler.

But what does this mean exactly? Well, let there be...

Differences

For a better understanding of what to expect, let's start with a collection of obviously "not so strictly typed" code snippets, and how to fix them.

Strict typing

There is no any or undefined for obvious reasons:

// 😢
function foo(a?) {
var b = a + 1;
return b;
}
// 😊
function foo(a?: i32 = 0): i32 {
var b = a + 1;
return b;
}

There are no union types:

// 😢
function foo(a: i32 | string): void {
}
// 😊
function foo<T>(a: T): void {
}

Objects must be strictly typed as well:

// 😢
var a = {};
a.prop = "hello world";
// 😊
var a = new Map<string,string>();
a.set("prop", "hello world");
// 😏
class A {
constructor(public prop: string) {}
}
var a = new A("hello. world");

The case of ===

Also, AssemblyScript does some things differently. It uses === for identity comparisons (means: the exact same object) for example. Idea is that its special JavaScript semantics for strings (same type and value) become irrelevant in a strict type context anyway. Some like this better, some hate it for portability reasons.

var s1 = "1";
var s2 = "123".substring(0, 1);
s1 === s1 // true
s1 === s2 // false
s2 === s2 // true
s1 === 1 // compile error
s1 == s2 // true

For all other types than string and operator-overloaded objects with a == overload, === is equivalent to ==.

1 === 1 // true
1 == 1 // true
obj === obj // true
obj == obj // true if there is no == overload doing something different

Imports

With WebAssembly ES Module Integration still in the pipeline, imports utilize the ambient context currently. For example

env.ts
export declare function doSomething(foo: i32): void;

creates an import of a function named doSomething within the env module, because that's the name of the file it lives is. It is also possible to use namespaces:

foo.ts
declare namespace console {
export function logi(i: i32): void;
export function logf(f: f64): void;
}

This will import the functions console.logi and console.logf within the foo module. Bonus: Don't forget exporting namespace members if you'd like to call them from outside the namespace.

Where automatic naming is not sufficient, the @external decorator can be used to give an element another external name:

bar.ts
@external("doSomethingElse")
export declare function doSomething(foo: i32): void;
// imports bar.doSomethingElse as doSomething
@external("foo", "baz")
export declare function doSomething(foo: i32): void;
// imports foo.baz as doSomething