Skip to content

Nova for C#, Java and TypeScript programmers ​

Nova's syntax was chosen to look like C# and TypeScript: braces, fn name(x: T) -> R, let and var, generics in angle brackets, public and internal, using, interfaces. Underneath, it works very differently: there is no garbage collector, no classes, no inheritance, no exceptions and no null references. Memory is managed at compile time, by tracking ownership.

At a glance ​

C# / Java / TypeScriptNovaNotes
var x = 1; / const x = 1;var x = 1; / let x = 1;
classstruct with methodsno inheritance; values, not references
interface, class C : Iinterface, impl I for Cinterfaces are generic bounds, not types
new Point(1, 2)Point { x: 1, y: 2 }on the stack, or Box.create(…) on the heap
garbage collectionownership: destroyed when the owner goes awaydeterministic, at a point the language defines
IDisposable, usingdrop { … }, usingevery owner runs drop, not only using
null referencesT?, which must be checkeda plain T is never null
exceptions, try/catchResult<T, E>, ?, matchpanics are bugs and cannot be caught
ref, outborrow mut T parameters, mut x at the call
List<T>, stringVector<T>, String / StringView
LINQ, lambdassortBy, forEach, any, all, retain; (x) => x > 2lambdas borrow and cannot escape
async / await, threadsnot yet
NuGet, Maven, npmnova.toml, path and git dependencies

The same program ​

csharp
class Account {
    public long Balance { get; private set; }
    public void Deposit(long amount) => Balance += amount;
}

var a = new Account();
a.Deposit(100);
Console.WriteLine(a.Balance);
nova
struct Account {
    private balance: i64;

    public fn create() -> Account => Account { balance: 0 };
    public fn deposit(borrow mut this, amount: i64) {
        this.balance += amount;
    }
    public fn current(borrow this) -> i64 => this.balance;     // a method cannot share a field's name
}

fn main() {
    var a = Account.create();
    a.deposit(100);
    Console.writeLine(a.current());
}
text
100

The receiver says how a method uses its object: borrow this reads it, borrow mut this changes it, and this takes it by value. Calling deposit needs a to be a var.

What changes ​

Values, not references. Assigning a struct copies it (if every field is copyable) or moves it, never shares it. Two variables never point to the same object unless one borrows the other, and a borrow is checked: while something is borrowed mutably, nothing else can use it.

Destruction is deterministic. An object is destroyed when its owner goes out of scope, by assignment, or when the object that owns it is destroyed. A drop block runs then, exactly once: closing a file or releasing a lock does not depend on a collector or a finally.

Errors are return values. A function that can fail returns Result<T, E>; the caller matches it, or passes it on with ?. Out of memory is an error value too. Bugs (an index out of range, an overflow) stop the program instead of throwing.

No null. StringView can never be null; StringView? can, and the compiler requires a check before use.

Generics are compiled per type, like C++ templates and unlike Java's erasure, and checked once, like C#'s. Interfaces constrain type parameters (<T: Shape>), but there is no List<Shape> holding different shapes: use an enum.

Things you will miss ​

Classes and inheritance, interfaces as types, reflection, attributes on everything, properties, events, LINQ's query syntax, string interpolation, exceptions, async, threads, and a large standard library.

Pre-1.0: the language and its tools are still changing.