The Jakt Programming Language
Jakt is a memory-safe systems programming language.
It currently transpiles to C++.
NOTE: The language is under heavy development.
NOTE If you're cloning to a Windows PC (not WSL), make sure that your Git client keeps the line endings as \n. You can set this as a global config via git config --global core.autocrlf false.
The transpilation to C++ requires clang. Make sure you have that installed.
jakt file.jakt
./build/file
See here.
The following strategies are employed to achieve memory safety:
In Jakt, there are three pointer types:
T.)T. Becomes empty on pointee destruction.)T. Only usable in unsafe blocks.)Null pointers are not possible in safe mode, but pointers can be wrapped in Optional, i.e Optional<T> or T? for short.
int. All casts must be explicit.For cases where silent integer overflow is desired, there are explicit functions that provide this functionality.
Far more time is spent reading code than writing it. For that reason, Jakt puts a high emphasis on readability.
Some of the features that encourage more readable programs:
object.function(width: 10, height: 5))enum scope. (You can say Foo instead of MyEnum::Foo).match.foo?.bar?.baz (fallible) and foo!.bar!.baz (infallible))foo ?? bar yields foo if foo has a value, otherwise bar)defer statements.. (never ->)ErrorOr<T> return type and dedicated try / must keywords.Jakt is flexible in how a project can be structured with a built-in module system.
import a // (1)
import a { use_cool_things } // (2)
import fn() // (3)
import relative foo::bar // (4)
import relative parent::foo::baz // (5)
import relative parent(3)::foo::baz // (6)
use_cool_things() from module a.Jakt has a Standard Library that is accessed using the jakt:: namespace:
import jakt::arguments
import jakt::libc::io { system }
The Jakt Standard Library is in its infancy, so please consider making a contribution!
When calling a function, you must specify the name of each argument as you're passing it:
rect.set_size(width: 640, height: 480)
There are two exceptions to this:
anon, omitting the argument label is allowed.There are two main ways to declare a structure in Jakt: struct and class.
structBasic syntax:
struct Point {
x: i64
y: i64
}
Structs in Jakt have value semantics:
struct instance always makes a deep copy.let a = Point(x: 10, y: 5)
let b = a
// "b" is a deep copy of "a", they do not refer to the same Point
Jakt generates a default constructor for structs. It takes all fields by name. For the Point struct above, it looks like this:
Point(x: i64, y: i64)
Struct members are public by default.
classSuper typeSelf typeSame basic syntax as struct:
class Size {
width: i64
height: i64
public fn area(this) => .width * .height
}
Classes in Jakt have reference semantics:
class instance (aka an "object") copies a reference to the object.Class members are private by default.
Both structs and classes can have member functions.
There are three kinds of member functions:
Static member functions don't require an object to call. They have no this parameter.
class Foo {
fn func() => println("Hello!")
}
// Foo::func() can be called without an object.
Foo::func()
Non-mutating member functions require an object to be called, but cannot mutate the object. The first parameter is this.
class Foo {
fn func(this) => println("Hello!")
}
// Foo::func() can only be called on an instance of Foo.
let x = Foo()
x.func()
Mutating member functions require an object to be called, and may modify the object. The first parameter is mut this.
class Foo {
x: i64
fn set(mut this, anon x: i64) {
this.x = x
}
}
// Foo::set() can only be called on a mut Foo:
mut foo = Foo(x: 3)
foo.set(9)
To reduce repetitive this. spam in methods, the shorthand .foo expands to this.foo.
Strings are provided in the language mainly as the type String, which is a reference-counted (and heap-allocated) string type.
String literals are written with double quotes, like "Hello, world!".
String literals are of type String by default; however, they can be used to implicitly construct any type that implements the FromStringLiteral (or ThrowingFromStringLiteral) trait. In the language prelude, currently only StringView implements this trait, which can be used only to refer to strings with a static lifetime:
let foo: StringView = "foo" // This string is not allocated on the heap, and foo is only a fat pointer to the static string.
Overloaded string literals can be used by providing a type hint, whether by explicit type annotations, or by passing the literal to a function that expects a specific type:
struct NotString implements(FromStringLiteral) {
fn from_string_literal(anon string: StringView) -> NotString => NotString()
}
fn test(x: NotString) {}
fn main() {
let foo: NotString = "foo"
test(x: "Some string literal")
}
Dynamic arrays are provided via a built-in Array<T> type. They can grow and shrink at runtime.
Array is memory safe:
Array keep the underlying data alive via automatic reference counting.// Function that takes an Array<i64> and returns an Array<String>
fn foo(numbers: [i64]) -> [String] {
...
}
// Array<i64> with 256 elements, all initialized to 0.
let values = [0; 256]
// Array<String> with 3 elements: "foo", "bar" and "baz".
let values = ["foo", "bar", "baz"]
fn main() {
let dict = ["a": 1, "b": 2]
println("{}", dict["a"])
}
// Function that takes a Dictionary<i64, String> and returns an Dictionary<String, bool>
fn foo(numbers: [i64:String]) -> [String:bool] {
...
}
// Dictionary<String, i64> with 3 entries.
let values = ["foo": 500, "bar": 600, "baz": 700]
fn main() {
let set = {1, 2, 3}
println("{}", set.contains(1))
println("{}", set.contains(5))
}
fn main() {
let x = ("a", 2, true)
println("{}", x.1)
}
match expressionsmatch armsmatch patternsmatch patterns?, ?? and ! operators…
Jakt supports both generic structures and generic functions.
fn id<T>(anon x: T) -> T {
return x
}
fn main() {
let y = id(3)
println("{}", y + 1000)
}
struct Foo<T> {
x: T
}
fn main() {
let f = Foo(x: 100)
println("{}", f.x)
}
struct MyArray<T, comptime U> {
// NOTE: There is currently no way to access the value 'U', referring to 'U' is only valid as the type at the moment.
data: [T]
}
namespace Greeters {
fn greet() {
println("Well, hello friends")
}
}
fn main() {
Greeters::greet()
}
There are two built-in casting operators in Jakt.
as? T: Returns an Optional<T>, empty if the source value isn't convertible to T.as! T: Returns a T, aborts the program if the source value isn't convertible to T.The as cast can do these things (note that the implementation may not agree yet):
true for any value except 0, which is false.false -> 0 and true -> 1, even for floats.T will increment the reference count as expected; that's the preferred way of creating a strong reference from a weak reference. A cast from and to raw T is unsafe.No open issues yet, or sync has not completed.