Any fool can write code that a computer can understand. Good programmers write code that humans can understand. – Martin Fowler.
Void has been recently changed paradigm, and now is willing to enter the world of native code. This opens up a new era of being able to write blazingly fast applications. Void aims to offer an easy and reliable syntax, that ensures that you can still write safe and simple code, without having to use a heavy runtime. The old compiler has been deprecated, and a new one is being developed at.
Void is an LLVM-based native programming language that is designed for developers to be able to create clean, fast and stable multi-platform applications with the power that native provides.
It has a relatively easy syntax, that follows most of the already existing code conventions.
Void features many utilities that enchant the experience of coding, such as object destruction, variable redeclaration, multi-returning, and much more.
Void compiles to LLVM IR, therefore it allows the application with the same exact code, to be compiled to all platforms, whilst forward and backward compatibility is guaranteed. This also makes it easy for Void to communicate with libraries written in other programming languages. It has many built-in designs, which makes it much easier to develop enterprise-grade applications.
Void is designed to satisfy the needs of multiple development paradigms. Unlike many production-oriented languages, Void does not force object-oriented programming on you, however it is recommended to use in most cases.
Below are few examples showcasing what Void provides to enchant programming experience.
Warning: This documentation was made way before, that Void had finally given a complete design, therefore expect some functionalities to be somewhat different when trying Void. Besides, some features may be not yet present in the following lines.
voidmain() {
println("Hello, World")
}You can declare variables using the "let" keyword. It automatically detects the type of the variable, but type can be also declared explicitly.
let value = 100In case of explicit type definition:
floatvalue = 3.5As you may notice, semicolons are not required. The compiler automatically places them after the end of lines, when it is required. This allows the chaining syntax, unlike many in case of many languages.
database
.fetchUser("username")
.transform(User::from)
.getOrThrow(err)In case of putting multiple statements in one line, you can separate them with putting a semicolon between them.
intf = 0; floatf = 10.0Although it is possible in Void, it is considered a bad practice of coding, as it can possibly overcomplicate a line of code. You may put only one statement in a line.
Normally languages doesn't let you declare variables twice, but as for some other languages such as Void, lets you to do that.
This is becuase in many cases you need to swap between variable types for a value, and having too much variables for a single value, or having long lines of type conversion is both considered a bad practice.
// bad codeintbalance = database.getUser("username").balancestringbalanceStr = $"{balance} USD"// also bad codestringbalance = string.from(database.getUser("username").balance) + " USD"// simplified code with variable redeclarationlet balance = database.getUser("username").balancelet balance = $"{balance}USD"Void lets you have methods return multiple variables at once.
This allows you to have more simple codes, without the need of making structs holding multiple values for return.
// overcomplicated codestructHttpResponse{publicintcodepublicstringmessage}HttpResponsefetchURL(stringurl){returnnewHttpResponse{code:404,message:"Not found."}}// simplified code(int,string)fetchURL(stringurl){return(404,"Not found.")}In the last example, a tuple is returned. A tuple can hold values of any length and any type. To access these values you can use a feature called tuple destruction.
let(code,message)=fetchURL("google.com")println($"Webserver responded with {code} status code")You can also access these values without destructing the tuple. You need to specify the index of the tuple you want to retrieve.
let response = fetchURL("google.com")let code = response.0let message = response.1Void allows you to name tuple members. Member names are specified inside the method return type declaration.
(booleansuccess,stringtoken)authenticate(Stringusername,stringpassword){return(true,"Authenticated.")}In this case, you can access these values by their names.
letresult=authenticate("admin","12345")if(result.success){println("Authenticated.")println($"Token: {result.token}")}else{println("Invalid credentials.")}Void features a wide variety of primitive types.
byte, short, int, long, float, double
And their unsigned version.
ubyte, ushort, uint, ulong, ufloat, udouble
You can specify the types of numbers in the number constant.
let byteVal = 30Blet shortVal = 150Slet intVal = 540// you don't need to put 'I' prefix, as non-decimal numbers are integers by default
let longVal = 120000Llet floatVal = 3.5Flet doubleVal = 40DThe most high-level one of types is the class type.
It has the capatibility of holding methods, implementations, fileds, and much more.
classCar {
stringtypeintspeedvoidmove() {
println("Moving...")
}
}In case of the need of classes, which are only for the purpose of holding values, you can use structs.
Structs are simplified classes with the purpose of holding values.
structPoint{
int x
int y
}let point = new Point{x:2,y:3}// you can use the simplified initializator, if Void knows the type of the structPointpoint = { x: -7, y: 0}
voiddrawPoint(Pointpoint) { /* do something */ }
drawPoint({ x: 30, y: 40 })You can create classes which only show you what methods they have, but they don't have an actual implementations.
interfaceCar {
voidmove()
} The methods are implemented by a class.
classFerrariimplementsCar {
@Overridevoidmove() {
println("Vrooooom...")
}
}You can have classes with constant members.
enumMimeType {
PLAIN_TEXT("text/plain"),
IMAGE("image/png"),
VIDEO("video/mp4")
stringdata;
MimeType(stringdata) {
this.data = data;
}
}You can decorate classes, fields, methods or code blocks with annotations.
@interface Subscribe {
stringevent
}
@Subscribe(event = "playerJoin")
voidonPlayerJoin() {
println("A player has joined the game.");
}Void features two types of access modifier declarations.
You can specify access modifiers separately for each methods and fields.
However this is not a good practise, as you are making method declarations much longer by having to put these manually everywhere.
publicstaticvoidfoo(){println("bar")}structPoint{publicintx,y}Alternatively, you can use "modifier blocks", which allows you to set the visibility modifiers of a section of code.
classEntity {
public:int posX, posY
int entityId
private:float health
float stamina
}Visibilitymodifiersarepublic, protectedandprivate.You can destruct object members in order to make them more accessible in the code.
structPoint{
int x, y
}letpoint=newPoint{x: 10,y: -2}let{ x, y }=pointprintln($"Point {x}, {y}")// you don't have to deconstruct all the variables of an objectlet{ x }=pointYou can create anonymus functions inside the code. These are also known as lambdas.
let foo = |int x| println(x)
foo(123)interfaceCallback {
voidhandle(intx, inty)
}
voidbar(Callbackc) {
c.handle(2, 3)
}
bar(|x, y| println(x * y))Using the default(Type) function, you can retrieve the default value of the given type.
letnumber=default(int)// 0letstate=default(bool)// falseletcar=default(Car)// nullVoid features a system, which allows to create custom default values for your own class types.
You should put a "default" modifier before the class declaration, and a default() method must be declared as well.
defaultclassCredentials {
stringusername, passworddefault() {
return {
username: "admin",
password: "12345"
}
}
}// initialize credentials to the default value let credentials = default(Credentials)// { username: "admin", password: "12345" }// you can use the default function without an explicit type, if the variable's type is defined explicitly
Credentials credentials = default// a simplfied syntax is also available
default Credentials credentialsNull-conditional operators provide null-safe access to members.
A '?' mark is placed before the instruction to make it null-conditional.
let profile = database.find("user")// the profile's balance is retrieved if the profile is not null, otherwise a default value is required
println(profile?.balance ?? 0)let person = getSomePerson()// might be null// the "walk" method is not invoked if "person" is null
person?.walk()// you can use null-conditional operators on nested members as well
person?.entity?.motion?.move()By default, variables live only in the scope of a method. Static variables however provide a way to statically store data in the method,
that is accessible for further method calls.
intgetIncrementId(){staticletcounter=0returncounter++}getIncrementId()// returns 0getIncrementId()// returns 1getIncrementId()// returns 2In Void you can use the old switch syntax.
switch (status) {
case200:
println("ok")
breakcase400:
println("error")
breakdefault:
println("unrecognized")
}However in most cases, this old design forces us to write a lot of unnecessary code, such as always having to write "break" after each cases.
Void's echanted switch allows you to simplify switch blocks and use them as direct expressions.
enumStatus {
SUCCESS,
FAILED,
UNKNOWN
}
switch (code) {
200 -> println("success")
400 -> println("failed")
else -> println("unknown")
}
// you can merge cases as wellswitch (status) {
FAILED|UNKNOWN -> println("Unable to authenticate")
SUCCESS -> println("Authenticated")
}You can use code blocks as expressions, including loops and switches.
letstatus=switch(code){200->SUCCESS400->FAILEDelse->UNKNOWN}StatusgetStatus(intcode) = switch (code) {
caseSUCCESS -> 200caseFAILED -> 400else -> 0
}
intcode = getStatus(SUCCESS)
// code is now 200Void allows you to merge an instruction with the return keyword. Therefore you don't need two extra lines to do a negated method guard.
// previously you had to use two lines of code to returnvoidhandleCommand(Playerp,stringcommand){if(!p.hasPermission("use")){p.sendMessage("no perms")
return
}// handle the command}// Void's syntax allows you to merge these two lines togetherif (!p.hasPermission("use"))
returnp.sendMessage("no perms")
// note that handleCommand returns void, so there are no conflicts returning something.// the return value of p.sendMessage (assuming it has one) is ignoredIn Void, you can queue post tasks for the method, that are going to be executed, when the method returns.
This can be used to prevent duplicating instructions whenever returning.
// prevously you had to do something like this// as you can see you need to call guard.unlock() 3 timesvoidmyThreadSafeMethod(){letguard=// get some lock for concurrencyguard.lock()if(myObject.someError){guard.unlock()return}if(myObject.someOtherError){guard.unlock()return}myObject.doSomething()guard.unlock()}In Void, you can replace these duplicates using the "defer" keyword. Deferred instructions will be executed whenever the method returns.
voidmyThreadSafeMethod(){letguard=// get some lock for concurrencyguard.lock()deferguard.unlock()if(myObject.someError)returnif(myObject.someOtherError)returnmyObject.doSomething()}Conditional variables are local variables that only exist in the scope of the condition. This prevents keeping unused object in the memory, and unnecessary variable name reservation.
// previously you had to code something like thisletcreated=createFolder("myFolder")if(!created)returnerror// Void allows you to simplify this code the following wayif(letcreated=createFolder("folder");!created)returnerrorWhen checking the isntance of objects, in older code practices, you had to first check the instance, then manually cast the value to the new type.
if(animalisParrot){letparrot=(Parrot)animalparrot.fly()}However this code requires us to write an extra line of code and manually cast the value.
if(animal is Parrot parrot){
parrot.fly()}You can name certain parts of code in order to allow jumps in scopes and nested loops.
// you may label your outer loop in order to allow "break" or "continue" on it.
myLoop:for(let i in1..10){for(let j in1..10){println($"{i}{j}")if(j == 5){break myLoop
}}}// you can put labels to non-blocks as well// this example shows a loop jump to a section
hello:
doSomething()
if (condition)
goto helloVoid features an async value retrieval system. In other programming languages, you might have seen these ad promises, tasks or futures.
Future<User>getUser(){returnFuture.completed(myUser)}letuser=getUser().get()Although Void lets you to explicitly declare future types, you should rather use the async/await syntax.
This automatically wraps the return type to be a Future, and calls get().
asyncUsergetUser(){returnmyUser}letuser=awaitgetUser()You can create threads as well and have full control over them.
letthread=newThread(||println("hello")).setName("my-thread").setPriority(0)thread.start()Void has a built-in JSON serializer, which allows you to directly map Void objects to JSON and vice versa.
// object to stringlet entity = @Json{entityId:100,meta:{
health:20,food:10,stamina:3},position:{x:200,y:4
z: -55}}let json = Json.serialize(entity)
println($"Data:{json}")// string to object
let data = "{ \"name\": \"admin\", \"userId\": 12345 }"
struct User{
string name
int userId
}
let user = Json.deserialize<User>(data)println($"Welcome,{user.name}")Void lets you have HTML code inside source code. By default, tags are mapped with Void's built-in tag system, however this can be extended.
letpage= @Html{<divclass="container"><h1>Hello, World</h1></div>}Void features direct bytecode instructions to be placed inside the source code.
int getSomeMagicValue(){let magic = 30
@Bytecodeunsafe{
ipush 100// push 100 to the stack
istore magic // store the value on the stack in the "magic" variable }return magic
}// the value of "test" will be 100let test = getSomeMagicValue()Void allows you to call native library methods. It also has a framework that makes it possible to interace with Void from native context.
// get the implementation of the method from native code@Link("library.dll")
publicnativeintmultiply(inta, intb)
// call the native implementation from Void contextletresult = multiply(2, 6)
println($"Result: {result}")// you can also link native methods of different signature@Link("library.dll")
@NativeTarget("bar")
nativevoidfoo(@NativeParam("int") inta)// in case of having multiple native methods in a class, you should annotate the class instead@Link("library.dll")
classMyNativeImplementation {
nativeintfoo()
nativevoidbar(floatf)
@NativeTarget("baz")
nativevoidsomething()
}letlibrary=newNativeLibrary().target(typeof(object))library.load()Void's built-in http module allows you to create efficient web servers with the express-js design.
voidmain(){// create a new server instance which will take care of handling the request routesletserver=newHttpServer()// create a GET request handler for the "/" routeserver.get("/",|req,res|// respond to the request with a plain text messageres.send("Hello, World"))// start the web server and listen on port 80server.listen(80)}