Skip to main content

Types

Freight is a type-safe language, meaning you must specify the types of all function arguments, return values, and declared variables inside your code. By ensuring that types are specified inside the code, the compiler can optimize certain code paths for better performance, as well as give better insight into your programs as they are being developed.

The following types are available in all programs without needing to import:

  • Number
  • Boolean
  • String

Structs

You can create custom product types using the struct keyword:

struct Foo {
bar: String
}

This will allow you to instantiate objects of type Foo:

new Foo { bar = 'foo bar' }

Enums

Freight also supports sum types by use of the enum keyword:

enum Result {
Ok { value: String },
Error
}

This sum type can be evaluated by pattern matching:

function fn_that_returns_result() -> Result {
if (!valid()) {
new Result::Error
} else {
new Result::Ok { value = "foo" }
}
}

value = match fn_that_returns_result() {
Ok(value) => { "it worked!" }
Error(error) => { "error" }
}

Sum types defined with enum allow functions to return different types of values depending on the execution.

Type Arguments

Arguments can be given to types in order to customize objects for other use cases:

struct Foo<Bar = String> {
bar: Bar
}

You can now instantiate Foo with a custom type for .bar:

foo = new Foo<Integer> { bar: 1 }

Abstract Types

You can designate types, functions, and even entire modules as abstract to specify an interface to your library without writing dummy code.

export package 'abstract' {
export module Foo {
abstract function Handler(input: String) -> Number

export function Server(handler: Handler) {
// compiler will assert that the function passed in implements `Handler`
}
}
}

Abstract modules are somewhat special, in that they designate everything under their scope to be abstract if they are themselves abstract:

export package 'abstract' {
export abstract module Bar {
// you don't need the `abstract` keyword for this function
function Handler(input: String) -> Integer
}
}

As such, modules cannot "know" if they are implementing a given interface, so you can "teach" the compiler that a module should be implementing an abstract module by using the implements keyword:

export package 'abstract' {
export abstract module Interface {
function send(message: Message) -> Boolean
function receive() -> String
}

export module Concrete implements Interface {
// this will error if exportable send/receive implementations do not exist.
}
}