Most ideas come from previous ideas - Alan C. Kay, The Early History Of Smalltalk
In the realm of low-level programming, language safety, performance and simplicity are paramount features. We are confident that these attributes can be achieved in system programming languages without substantially sacrificing expressiveness. Concrete achieves this balance, offering a programming language that is fast, simple and safe without having a garbage collector, lifetimes and a complex borrow checker. Additionally, it features a default pluggable runtime, enabling the development of highly scalable systems that are not only reliable and efficient but also straightforward to maintain.
Writing good code should be easy. The language must be simple enough to fit in a single personβs head. Programs are about transforming data into other forms of data. Code is about expressing algorithms, not the type system. We aim to develop a simpler version of Rust that includes an optional default runtime featuring green threads and a preemptive scheduler, similar to those found in Go and Erlang.
Building is as simple as cloning this repository and running the make build
command, provided you have all the needed dependencies.
Make sure you have installed the dependencies:
- git
- Rust
- LLVM 18 with MLIR enabled
- libgit2
If building LLVM from source, you'll need additional tools:
- g++, clang++, or MSVC with versions listed on LLVM's documentation
- ninja, or GNU make 3.81 or later (Ninja is recommended, especially on Windows)
- cmake 3.13.4 or later
- libstdc++-static may be required on some Linux distributions such as Fedora and Ubuntu
Setup the following env vars:
# For Debian/Ubuntu using the repository, the path will be /usr/lib/llvm-18
export MLIR_SYS_180_PREFIX=/usr/lib/llvm-18
export LLVM_SYS_180_PREFIX=/usr/lib/llvm-18
export TABLEGEN_180_PREFIX=/usr/lib/llvm-18
If you installed llvm with brew, source the env-macos.sh
script to set up the needed env vars:
source scripts/env-macos.sh
Concrete take many features from Rust like:
- Enum, structs instead of classes
- Ad-hoc polymorphism via traits
- Parametric polymorphism via generics
- Expressions and statements rather than only expressions as in many functional languages
- Built-in dependency manager
- Built-in linter and formatter
- Built-in testing tooling
- Good compilation error messages
- Inmutability by default, optional mutability
But we want to take a different path with respect to:
- Linear type system rather than affine type system
- No lifetimes
- Simpler borrow checker
- Concurrency model. We provide a default runtime with green threads. There is no support for low-level primitives like atomics, mutex and OS threads.
- There is no Sync and Send traits. This implies that mutability can only happen inside the same process.
- No relationsihp between modules and files
- No circular dependencies in modules
- No macros
- At the beginning we won't support local type inference at function level. We might add it in the future.
- Financials decimal type and bigint type as part of the standard library
- Safe FFI
- Perfect replayability to be able to easily reproduce Heisenbugs
- We want to try to have bit-for-bit deterministic/reproducible builds from the beginning. This will be difficult to have but we will do our best.
- C/Go/Rust-inspired, context-free small grammar, syntax: if, for, function calls, modules, pattern matching
- Safe. Linear types that allow memory and other resources to be managed safely and without runtime overhead
- Small core. The entire language specification should be possible to be memorized
- Performant as C/C++ or Rust
- Pluggable concurrency runtime with a preemptive scheduler, green threads and copy only message passing
- Profiling and tracing are integral, first-class features. While code is read more often than it's written, it is executed even more frequently than it's read
- There ought to be one way to write something. Striving for orthogonality
- Explicit over implicit
- Cross compilation as first class citizen
- Easy C, C++ and Rust FFI
- Easily embeddable
- Capability based security to defend against supply chain attacks
- Type inference only within blocks, not in function signatures
- Algebraic Data Types
- Traits and Generics
- REPL for connecting to running services and for quick iteration
- Compile to MLIR, WASM and generate C code
- Financials decimal type and bigint type as part of the standard library
- Implemented in Rust
- No hidden memory allocation
- No garbage collection or destructors
- No hidden control flow or implicit function call
- No preprocessor, no macros
- No global state
- No exceptions
- No type inference, type information flows in one direction
- No implicit type conversions
- No reflection
- No function overloading (except through typeclasses, where it is bounded)
- No uninitialized variables
- No pre/post increment/decrement (x++ in C)
- No variable shadowing
- No Java-style @Annotations
- No undefined behavior
- No marker traits like Send, Sync for concurrency. The runtime will take care of that.
mod Fibonacci {
fn main() -> i64 {
return fib(10);
}
pub fn fib(n: u64) -> u64 {
if n < 2 {
return n;
}
return fib(n - 1) + fib(n - 2);
}
}
mod StructExample {
struct Foo {
bar: i32,
baz: i64,
}
fn main() -> i32 {
let mut foo: Foo = Foo {
bar: 2,
baz: 3,
};
foo.bar = foo.bar * 2;
return get_foo_field_by_borrow(&foo) + foo.bar;
}
fn get_foo_field_by_borrow(x: &Foo) -> i32 {
return x.bar;
}
}
mod Option {
pub enum Option<T> {
None,
Some(T),
}
pub fn map<A, B>(opt: Option<A>, f: A -> B) -> Option<B> {
match opt {
None -> None,
Some(x) -> Some(f(x)),
}
}
}
mod UsesOption {
import MyOption.{Option, map};
pub fn headOfVectorPlus1(x: [u8]) -> Option<u8> {
// head returns an option
x.head().map((x: u8) -> x + 1)
}
}
The design was very heavily influenced by all these programming languages:
We want to thanks everyone of them. We also took core ideas, specs and features from these languages:
For a full roadmap check the project.
Meaning:
- βοΈ = implemented
- ποΈ = work in progress
- β = work not started yet
- π€ = to be defined
Features:
- if/else βοΈ
- while βοΈ
- modules βοΈ
- imports βοΈ
- floats βοΈ
- borrowing βοΈ
- structs βοΈ
- casts βοΈ
- arrays ποΈ
- iterators β
- for ποΈ
- match β
- option β
- enums β
- impl β
- linear type checker ποΈ
- borrow checker β
- generics β
- traits β
- unsafe β
- box β
- rc (for cyclical data structures like graphs) β
- ffi β
- operating system threads with move only semantics β
- rayon-like β
Post-runtime features:
- runtime with preemptive green threads β
- erlang like profiling β
- erlang like tracing β
- erlang like observer β
- standard library β
- gleam otp like library patterns β
- http server β
- json β
- sql server β
- serde replacement β
- rustler like binding generator to call rust code β
- embeddable concrete (no allocator, first-class support for no standard library, etc) β
- capabilities π€
There are some simple program benchmarks against Rust.
You can run them using the following make target:
make bench