When you first see Cello, it's hard to understand exactly what it is. You might think of Cello a framework, or perhaps as a syntax layer, or even a totally new programming language, but the best way I've found to think of it is as A Fat Pointer Library. Let me explain...
Fat Pointers were proposed by Walter Bright (inventor of the D programming language) as an extension to C, aimed to avoid the numerous pitfalls that come from the relationship between pointers and arrays.
The problem is this. In C when you pass an array to a function it decays to a pointer - meaning what you actually pass is just a pointer to the first element in the array. This means there is no way to know how many elements are in the Array you've been passed.
The conventional way to deal with this is to also pass in the array size separately - or - as is done by C strings, use some special value to represent the end of the array. But both of these methods are famously prone to abuse. It is simply too unreliable to get programmers (or malicious users) to provide this extra information accurately.
Walter's proposal was simple. Introduce some new syntax for passing a Fat Pointer. This is just a pointer with the additional piece of information saying how many elements are in the array. Now this wouldn't do anything to fix existing C code, but would hopefully improve future C, and because Fat Pointers would still just be pointers of a sort, it wouldn't break anything along the way.
This proposal wasn't accepted into the C standard, but it turns out this sort of thing doesn't have to be a language extension, we can actually emulate Fat Pointers in C ourselves.
The trick is to place the value representing the number of items in the array in memory just before the pointer we actually pass to functions. This pointer is fully compatible with normal pointers, and if we need to know how many elements it points to we just move back a step in memory to get it.
|--------------| <-- Pointer to number of items
| ptr.length |
|==============| <-- Pointer passed to functions
| ptr.item[0] |
|--------------|
| ptr.item[1] |
|--------------|
| .... |
|--------------|
This is exactly the approach used by the C strings library sds. This allows for strings that are safer, faster, and more efficient than C strings, but which are also compatible with standard C string functions.
The only caveat is that because the pointer passed around is not actually the
address we got from the memory allocation routine (such as malloc
) it can't be
freed or resized in the normal way with free
or realloc
. Instead we have to
move back a step in memory and free that pointer. So allocation, freeing, and
resizing must all be done via special functions. But any good C interface
should not assume pointers are heap allocated anyway.
To change a language's behaviour one must either change the compiler or the runtime. A language like C has no runtime system but we can add one ourselves. We can tag objects with some extra information which our runtime system can make use of. In old versions of Cello this was done by directly embedding the information in C structs that were Cello compatible. This was okay but it meant specfically editing every struct we want to make avaliable for use with Cello.
Instead we can use the ideas behind Fat Pointers, and store our runtime
information in the memory space before pointers. In essence we make all our
pointers to Cello objects Fat. Then Cello pointers would be fully
compatible with standard C pointers and functions (with the exception of free
and realloc
), but also have the information to be used by our runtime system.
In this sense our runtime really does extended the language rather than
create something that is conflicting.
In Cello this extra information is pretty simple. All we store is a pointer to
the type such as Int
, Float
or Array
.
typedef void* var;
struct Header {
var type;
};
We can then allocate Fat Pointers on the heap with some simple pointer arithmetic. Here is a modified version of the Cello function that does that.
var alloc(size_t data_size) {
struct Header* head = calloc(1, sizeof(struct Header) + data_size);
head->type = type;
return ((var)head) + sizeof(struct Header);
}
And here is the deallocation function.
void dealloc(var self) {
struct Header* head = self - sizeof(struct Header);
free(head);
}
We can also allocate Fat Pointers on the stack. This takes two steps. First we
allocate memory big enough for the data using a char[]
and then we call a
function to copy in the header details and return a pointer offset to point
to the data.
#define alloc_stack(T) header_init( \
(char[sizeof(struct Header) + sizeof(struct T)]){0}, T)
var header_init(var head, var type) {
struct Header* self = head;
self->type = type;
return ((char*)self) + sizeof(struct Header);
}
We can also copy in some initial values to the members of this structure
allocation. In Cello this is the $
macro. It allocates an anonymous structure
of the given type, with the given members, and copies it into the allocation
found by alloc_stack
.
#define $(T, ...) \
memcpy(alloc_stack(T), &((struct T){__VA_ARGS__}), sizeof(struct T))
And now this gives us all we need to get started with our runtime. The next step is to design the format of the extra information tagged onto our data - the type.
The type is the meta-data associated with an object which tells us how it
should behave. For example if we wish to add some generic new
and del
to a
language, our type must be able to tell us the size of the memory to
allocate, or what action to perform on construction / destruction.
How we design our type data should reflect the features we want available in
the language. In Cello I chose a very simple design. The type object is just a
list of instances of Type Classes
(a.k.a Interfaces) which the object implements and some identifier for each.
For example say we want some type to be able to be get
or set
using some
keys and values - then we can provide it with an instance of the Get
type
class.
In Cello a type class is just a description of various behaviours with names, so it can be easily represented as a struct full of function pointers.
struct Get {
var (*get)(var, var);
void (*set)(var, var, var);
var (*mem)(var, var);
void (*rem)(var, var);
};
The actual type object structure in Cello is pretty complicated, but a simplified example might end up looking something like this under the hood.
var Int = (struct TypeEntry[]){
{ "Size", &((struct Size){ Int_Size })
{ "New", &((struct New){ Int_New, Int_Del }) },
{ "Cmp", &((struct Cmp){ Int_Cmp }) },
{ NULL, NULL }
};
Then when (for example), we wish to call new
to make an Int
, we just lookup
the type object, and if it implements the New
type class we can find the
function pointer for that particular instance of the class, and call the
function. This is very much like the concept of a
vtable from C++.
if (type_implements(type, New)) {
struct New* inst = type_instance(type, New);
return inst->new(self, args);
}
Perhaps the most surprising thing about this is that this single feature (of type classes or interfaces) is powerful enough to support all of the advanced features you see in Cello. It isn't just used for things like constructors and destructors, it is used for everything, ranging from Garbage Collection to generating the Documentation.
It is well known now that family style inheritance is generally pretty bad for structuring things, and that single interface inheritances tends to work a lot better - but Cello is a good example of how neat and practical a library can be designed around this single concept - and that if you are really forced to stick to it you end up writing a lot of code in a very algorithmic, expressive, and readable way.
Cello is a Fat Pointer Library. It lets you create pointers which are tagged with additional runtime information, which can then be used for a whole host of other tasks and features. In reality it is a little more complicated than the shallow explanation here, but hopefully this gives you a vague idea of how it works under the hood.
I've always tried to be objective about Cello. It is a hack on top of C, and hacks don't always end up celebrated. I've admitted there are many reasons I wouldn't use it for a project, and I don't expect others to either. I'm sometimes uncomfortable singing it's praises because I know it has issues. But I also think it has potential.
I believe the reason people get excited about Cello is because they see it similarly to what underscore.js or jquery did for Javascript. Suddenly a language that appeared stale, exhausted, and difficult was transformed into something that looked, and felt, completely different. Even if the transformation was somehow shallow underneither it is still very cool to see this happen just using a library. This inspired a lot of people, and a lot of libraries started popping up. The Javascript floodgates opened and it became something totally different.
Cello still has a lot of oddities and awkard bits, but so far Cello is the only Fat Pointer Library I know of that tries to do everything, packaging it up in a nice syntax. The key thing to take away from this project is that it isn't entirerly unsuccessful, and that Cello is just one design of a runtime. There are many possible ways in which a Runtime could be designed with different strengths and ideas. I'd love to see what other people can do with this combination of thoughts, and just perhaps the Fat Pointer has the potential to give C another breath of life, like Javascript was so lucky to have.