Skip to content

The case for comptime reflection (or how to properly define the Drop trait for a generic type) #188

@davidscholberg

Description

@davidscholberg

Zen C now has RAII via the Drop trait as well as generics, and these two features have engendered a problem that I think is ideally solved via comptime reflection.

The problem arises when you have a generic container type which can hold types that may or may not implement the Drop trait. As of the current version of Zen C, I don't think there's a way for Zen C code to be able to check if a particular type implements a particular trait (neither at comptime nor runtime), and as such I don't think it's possible to properly define the Drop trait for a generic container type if you want the container to be able to drop its contained elements.

Here is a Zen C program that illustrates this:

include <stdlib.h>

struct Droppable {
    n: int;
}

impl Drop for Droppable {
    fn drop(self) {
        printf("dropped Droppable: %d\n", self.n);
    }
}

struct Array<T> {
    arr: T*
}

def ARR_SIZE = 3;

impl Array<T> {
    fn new() -> Array<T> {
        return Array<T> { arr: malloc(sizeof(T) * ARR_SIZE) };
    }
}

impl Drop for Array<T> {
    fn drop(self) {
        // Ideally this loop should be conditionally included at compile time
        // only if T implements Drop
        for i in 0..ARR_SIZE {
            self.arr[i].drop();
        }

        free(self.arr);

        printf("dropped Array\n");
    }
}

fn main() {
    // Everything is properly freed because Droppable implements Drop
    let a1 = Array<Droppable>::new();
    a1.arr[0] = Droppable { n: 1 };
    a1.arr[1] = Droppable { n: 2 };
    a1.arr[2] = Droppable { n: 3 };
    for i in 0..ARR_SIZE {
        printf("check out this array element: %d\n", a1.arr[i].n);
    }

    // Won't compile because int doesn't implement Drop
    let a2 = Array<int>::new();
    a2.arr[0] = 1;
    a2.arr[1] = 2;
    a2.arr[2] = 3;
    for i in 0..ARR_SIZE {
        printf("check out this array element: %d\n", a2.arr[i]);
    }
}

When you have an array of a droppable type, the array's drop method should also call the drop method for each of its elements before freeing the memory allocated for the array. If, however, the contained type does not implement Drop, then the array's drop method only needs to free its own memory. Ideally, the determination to drop or not drop the array elements happens at compile time because a) that info is technically available at compile time, and b) that would eliminate unnecessary runtime overhead.

I'm not sure if you've given any thought to comptime reflection yet, but from an api perspective it could look something like this:

impl Drop for Array<T> {
    fn drop(self) {
        comptime if T.implements(Drop) {
            // The code in this block is only included in the resulting C file if T implements Drop
            for i in 0..ARR_SIZE {
                self.arr[i].drop();
            }
        }

        free(self.arr);

        printf("dropped Array\n");
    }
}

Thoughts on this?

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions