Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.Sign up
Flect is a functional systems programming language. It pragmatically combines features such as algebraic data types, pattern matching, first-class function values, traits, generics, macros, and high type safety with (optional) low-level control over memory, direct interfacing to C and C++, bare metal compilation support, and so on.
Flect targets any operating system (or none), architecture, and ABI that the C99 compiler it uses supports. It is compiled to C99 in order to benefit from existing compiler technology and to avoid having a separately maintained code generator for every supported target. This strategy makes the language highly portable compared to other systems languages.
Flect's language design has been influenced and inspired by many languages. Most notable are C, Rust, Haskell, ML, and Lisp. Some concepts have also been borrowed from C++, D, F#, and Elixir.
Why create yet another so-called systems language?
First of all, the term systems language has become very skewed in recent times. These days, any language that has pointers will usually be accepted as a systems language by most programmers. It has even been used to mean a language that's suitable for large-scale development.
We think that's wrong. We think the real definition of a systems language is this: A language that gives you low-level control over execution, memory, and hardware and supports bare metal programming with no assumptions about host operating system or automatic memory management mechanism.
There are very few languages in existence that fit this definition. C, C++, and Ada are a few worth mentioning. D, Rust, and Nimrod can be argued to fit this definition to some extent.
Flect aspires to be a real systems language. What motivated the creation of Flect was, among other things:
- Modern language design: Other systems languages are very old and do not incorporate modern language design research. This makes them very unsafe; shooting yourself in the foot is very easy with C-dialect languages, for instance.
- Abstractions: Some systems languages (such as C) do not have any good features for abstraction other than functions. They also lack generics and sub-typing. This is universally considered sub-optimal for large-scale programming today.
- No OOP: A few systems languages insist on using the object-oriented programming paradigm. Though ubiquitous, most programmers are starting to consider it unsuitable for many tasks today. We don't like it.
- Functional programming: Most systems languages do not have convenient functional features such as first-class functions, closures, algebraic data types, pattern matching, and so on.
- Portability: Most other systems languages are not very portable in practice. They either assume things about the target architecture or just aren't available on some architectures.
- Safety: Through good safety measures in the language (such as no uninitialized data) and built-in instrumentation features, bugs are easy to find and squash or never even make it into code in the first place. This is contrary to languages like C.
- Static correctness: Other languages have serious issues with things like pervasive null pointers and double frees (and similar memory bugs), lack of type safety (implicit truncation of integral types), and undefined behavior (e.g. integer overflows and evaluation order of function arguments). Such issues should not exist in a modern systems language.
- Memory management: There is a general lack of good memory management techniques in systems languages; most of them make you manage your memory manually or impose a GC on you. We want different and flexible ways of managing memory built into the language.
- Control: Systems languages generally don't have standard ways to write inline assembly, perform volatile memory reads and writes, and so on. These are essential features to standardize in a systems language.
Flect is a systems language and so has many features that are invaluable when doing low-level programming. It combines these features with concepts from functional programming languages to create a highly expressive language supporting modern methods of abstraction. Further, the language is refined to disallow unsafe operations by default, requiring those to be in explicit unsafe sections.
All in all, this makes for a highly productive and powerful language.
Algebraic data types: Plain old C-style
structs (also known as records) are supported, as are discriminated
unions (i.e. tagged unions). This makes it easy to express alternative code paths in the type system.
Tuples: Tuple types are supported natively in the type system and the language has tuple packing, unpacking, and matching syntax. This makes for an easy way to do multiple return values, for instance. They are super efficient as they are the equivalent of passing a C
Immutability: Everything in Flect is immutable by default. If you want mutability, you specify it explicitly with
mut. Further, functions can choose to take types with
immelements (an array, for instance) such that both mutable and immutable elements can be passed. This makes it easy to specify verified mutation guarantees.
Generics: Flect supports generics through type substitution and constraints based on traits. This makes implementing type-generic functions both easy and very efficient. For instance, if a generic parameter is constrained to the built-in
Numtrait and instantiated with an
int, the compiler can trivially optimize all operations to compile to native instructions for e.g. additions, subtractions, etc.
- Type information: Flect provides access to type information at both compile time and run time. The garbage collector makes use of type information for efficient and precise scanning.
Expression syntax: Everything in Flect that would traditionally be considered a statement in C is actually an expression. For instance, an
ifexpression returns the last value in its true path or the last value in its false path. This removes a lot of boilerplate such as setting local variables and performing explicit returns of them.
- Local type inference: Types of local variables are inferred from their assigned expressions, removing a lot of unnecessary noise from code. In the interest of readability, however, function signatures must be specified explicitly.
- Pattern matching: Flect supports pattern matching on all sorts of values as seen in languages such as ML and Erlang. The compiler performs exhaustiveness checking, so cases in a discriminated union are never missed.
- Modules: Instead of header or interface files, Flect has a simple and elegant module system. Interface information is stored in small binary files shipped alongside libraries produced with the Flect compiler. These are written automatically by the compiler; there is no maintenance burden on the programmer.
- Traits and implementations: A trait describes a set of operations that can be performed on a type conforming to the trait's interface. A type can conform to an interface by having an associated implementation of a trait. This system is similar to type classes.
Macros: Support for hygienic, Lisp-style macros (based on
unquote) makes it easy to build syntax abstractions on top of the existing language. The Flect compiler also has a CTE (compile-time evaluation) engine that can run code at compile time, making it easy to generate code statically.
Safety contexts: Blocks, functions, and even entire modules can be marked
safein Flect. In a safe context, code that needs to subvert the type system, or do inherently unsafe things such as pointer arithmetic, must be put in an explicit
unsafesection. This helps isolate fragile code.
Well-defined: Flect has very little undefined behavior. The few things that are undefined (such as dereferencing invalid pointers, mutating immutable memory, and accessing out-of-scope variables) are only so by necessity. Most things with undefined behavior can only be done in
- C compilation model: Flect is compiled ahead-of-time, meaning it produces native executables and static/shared libraries. This also means it naturally links to C and C++ code.
- C/C++ FFI: There is full support for interfacing with C in Flect; the Flect compiler will conform to the target platform's application binary interface. There is also limited support for C++ interfacing (no classes and templates).
- UTF-8 strings: All strings are stored as UTF-8. Combined with UTF-8 source code documents, this makes for a very localization-friendly language suitable for information exchange.
Inline assembly: Flect supports any assembly syntax that the target assembler does. It has a standardized
asmexpression which makes writing inline assembly slightly saner than in e.g. C.
- Volatile memory access: Intrinsics are provided to perform volatile memory reads and writes. This makes interfacing with external hardware both easy, safe, and fast.
Flexible memory management: Memory can be managed in a variety of convenient ways in Flect. Data can be put on the stack with value semantics, it can be stored in
@(GC-managed) pointers, or it can be managed with plain old
*(unsafe) pointers. A common pointer type,
&(general pointer), makes it easy to write code that works with both managed and unsafe pointers.
- Tail calls: The Flect compiler tries very hard to transform tail recursion into proper tail calls (i.e. plain jumps to sibling functions). This makes writing recursive algorithms generally safe.
Labels and jumps: The good old
gotostatement (or more accurately, expression in Flect) is supported. Further, labels have values, which means that things such as very fast dispatch tables can be built from them.
Unit tests: Flect has built-in support for unit
testblocks. This means that unit tests can be kept alongside the implementation code. This makes for easy maintenance and also serves as good documentation.
Assertions: Flect has a built-in
assertexpression. It specifies that the given condition must hold true or program execution cannot continue. Thus, the asserted condition is also used for optimization purposes (i.e. to propagate invariants to optimizers).
- Native code: Flect compiles to native machine code. No virtual machine is required, regardless of whether it is compiled for a particular OS or for bare metal.
- Instrumentation: The Flect compiler has a variety of helpful instrumentation modes to help find memory, arithmetic, and concurrency errors at run time.
- Static analysis: The Flect compiler has a static analyzer built-in that attempts to locate common programming errors in Flect source code that aren't normally covered by the language's type system, such as impossible conditionals, invalid pointer dereferences, division by zero, and so on.
- Documentation generation: The Flect compiler comes with a documentation generator that can generate pretty HTML documentation (among other target formats).
- High portability: Flect makes very few assumptions about the target architecture and operating system. This makes it trivial to port to new platforms. Today, it runs on Linux, OS X, FreeBSD, and Windows, to name a few, and supports architectures such as x86, ARM, PowerPC, and more.