KonLang Tiếng Việt

Language tour

This is Kon as the compiler runs it today. Every complete program here was built and run by that compiler, and its output is what you see under it.

Hello

func main(): i32 {
    print("xin chào")
    return 0
}
xin chào

main returns the program's exit code. Lines end statements, so there are no semicolons. Run it with kon run hello.kon.

Values

let binds once and var can be reassigned. Types are usually worked out for you, and you write them where a plain number could be several things.

func main(): i32 {
    let name = "Kon"
    var count: i32 = 1
    count += 41
    let ratio: f64 = 1.5
    let small = count as u8
    print(name)
    print(count)
    print(ratio)
    print(small)
    return 0
}
Kon 42 1.5 42

The number types are i8 to i64, u8 to u64, f32 and f64. Conversions are always written with as. Integer arithmetic wraps around when it overflows, the same way on every machine, and dividing by zero stops the program. A float converted to an integer saturates at the type's limit.

Functions

const LIMIT: i32 = 100

func clamp(value: i32): i32 {
    if value > LIMIT { return LIMIT }
    return value
}

func main(): i32 {
    print(clamp(250))
    return 0
}
100

Control flow

func main(): i32 {
    var total: i64 = 0
    for i in 0..(5 as i64) {
        if i == 3 { continue }
        total += i
    }
    var left: i32 = 3
    while left > 0 {
        left -= 1
    }
    print(total)
    print(left)
    return 0
}
7 0

Structs and methods

Methods live inside the struct. The first parameter says what the method does with it: view self reads and edit self changes.

struct Counter {
    value: i32

    func get(view self): i32 {
        return self.value
    }

    func bump(edit self, by: i32) {
        self.value += by
    }
}

func main(): i32 {
    var counter = Counter { value: 40 }
    counter.bump(2)
    print(counter.get())
    return 0
}
42

Enums and match

A variant can carry data, and match must cover every case.

enum Shape {
    Circle(f64)
    Square(f64)
    Empty
}

func area(shape: view Shape): f64 {
    match shape {
        Shape.Circle(radius) => { return 3.14 * radius * radius }
        Shape.Square(side) => { return side * side }
        Shape.Empty => { return 0.0 }
    }
    return 0.0
}

func main(): i32 {
    let square = Shape.Square(3.0)
    print(area(square))
    return 0
}
9

Option and Result

There is no null. A value that may be missing is an Option, and try passes a missing value or an error up to the caller.

func half(value: i32): Option<i32> {
    if value % 2 != 0 { return none }
    return some(value / 2)
}

func quarter(value: i32): Option<i32> {
    let once = try half(value)
    return half(once)
}

func main(): i32 {
    match quarter(20) {
        some(result) => { print(result) }
        none => { print("not whole") }
    }
    let missing = quarter(6)
    print(missing.isSome())
    return 0
}
5 false

Lists and arrays

List grows on the heap and fixed arrays do not. Every index is checked: going past the end stops the program instead of reading other memory.

func main(): i32 {
    var scores: List<i32> = list[]
    scores.add(7)
    scores.add(35)
    var total: i32 = 0
    var at: u64 = 0
    while at < scores.length {
        total += scores[at]
        at += 1
    }
    print(total)

    let fixed: [u8; 3] = [1, 2, 3]
    print(fixed[2 as u64])
    return 0
}
42 3

Text

str is text you only look at, and String is text you own and can grow. Both are UTF-8. Asking for "the length" is an error on purpose, because bytes, characters and visible letters are three different numbers.

func main(): i32 {
    var greeting = String("xin ")
    greeting.append("chào")
    print(greeting)
    print(greeting.byteLength)
    print(greeting.characterCount())
    return 0
}
xin chào 9 8

For numbers, splitting, searching and files, see std/text.

Ownership

Every value has one owner, and the owner frees it, with no garbage collector. When you pass a value, the call shows what happens to it:

You writeMeaningAfterward
show(player)The function reads it; view needs no mark at the callYou still own it
heal(edit player, 5)The function changes it, and nothing else may touch it meanwhileYou still own it
keep(take player)You hand it overYou can no longer use it

Using a value after handing it over does not compile, and the error names the line:

func main(): i32 {
    var items: List<u8> = list[]
    let other = take items
    print(items.length)
    return 0
}
K2-OWN-001: value was moved or is not initialized at game.kon:4:11

Neither does changing a value while something else is reading it:

func copyInto(target: edit List<u8>, source: view List<u8>) {
}

func main(): i32 {
    var items: List<u8> = list[]
    copyInto(edit items, items)
    return 0
}
K2-BORROW-001: access conflicts with a live borrow at game.kon:6:26

Moving a named value must be visible, so forgetting take is also an error:

func keep(items: List<u8>) {
}

func main(): i32 {
    var items: List<u8> = list[]
    keep(items)
    return 0
}
K2-OWN-002: named owning value requires explicit take at game.kon:6:10

Generics and interfaces

interface Named {
    func label(view self): i32
}

struct Cat implements Named {
    lives: i32

    func label(view self): i32 {
        return self.lives
    }
}

func labelOf<T>(item: view T): i32 where T: Named {
    return item.label()
}

func main(): i32 {
    let cat = Cat { lives: 9 }
    print(labelOf<Cat>(cat))
    return 0
}
9

Modules

A file is a module, and only pub names are visible outside it. An import looks beside the source file first, and std/ paths then fall back to the library that ships with the toolchain.

import "std/text.kon" as text

func main(): i32 {
    let number = text.fromInt(42)
    print(number)
    return 0
}
42

Cleanup with defer

defer runs when the block ends, in reverse order, however it ends.

func main(): i32 {
    defer {
        print("last")
    }
    defer {
        print("second")
    }
    print("first")
    return 0
}
first second last

Calling C

Foreign calls are the one place the compiler cannot check, so they must sit inside unsafe, where a reader can find them.

extern "C" func abs(value: i32): i32

func main(): i32 {
    var result: i32 = 0
    unsafe {
        result = abs(-42)
    }
    print(result)
    return 0
}
42

Not in the language yet

The specification goes further than the compiler does so far. These do not work today: closures, text interpolation such as "n = {n}", enums that hold a List of themselves, and automatic conversion between str and String. The status page has the full picture.