Baby Steps

A blog about programming, and tiny ways to improve it.

Iface Types

Yesterday I wrote about my scheme for paring down our set of function types to one type, fn:kind(S) -> T. When I finished writing the post, I was feeling somewhat uncertain about the merits of the idea, but I’m feeling somewhat better about it today. I really like the idea that top-level items have the type fn:kind(S) -> T and that you therefore give them an explicit sigil to use them in an expression; this allows us to remove the “bare function” type altogether without any complex hacks in the inference scheme.

Anyway, I didn’t talk at all about iface types yesterday, but they have a place in this scheme too. An iface type, also called a boxed iface, is basically the pair of a vtable with a self pointer. Today this is hard-coded to be a GC’d ptr (@), but I want to change this as it is very limited: iface types are relatively expensive to construct (requiring allocation, RC overhead, etc) and they cannot be sent between tasks.

Under my proposal, an iface type would be written id:kind where id is the name of the interface and kind is an optional kind bound that applies to the receiver. The type is dynamically sized, because the value that is represented is something like:

struct iface_instance {
    void *vtable;
    type_desc *td;
    ... // self data is represented inline

This proposal therefore allows you to construct things like:

@id      (today's "boxed iface")
&id      (an iface instance allocated on the stack)
~id:send (a sendable iface instance)

New interface instance construction syntax

There is one other change I’d like to make, which is independent but seems to fit. Today, iface types are constructed using as. I am not crazy about this because as is normally our type cast operator, but iface type construction is not a type cast. It may perform allocation etc. The as construction is also very wordy, requiring one to specify the desired iface rather than having it inferred, and it has an awkward requirement that :: be used for any type parameters on the type.

As a replacement I propose we make use of the iface keyword in expressions, so that iface::<T>(v) would construct an instance of the iface type T for the value v. Like all type parameters, T may be left off and inferred from context. So typically you would just write iface(v), as in this example (here I assume the current iface types, rather than the ones I will describe shortly):

iface an_iface<T> { ... }
impl of an_iface<int> for int { ... }
fn foo(i: an_iface<int>>) { ... }
fn bar(i: int) { foo(iface(i)) {

In contrast, the fn bar in the old syntax looks like:

fn bar(i: int) { foo(i as an_iface::<int>) }

At first I wanted to make ifaces into constructor functions with a signature like:

fn an_iface<I:an_iface>(i: I) -> an_iface

but this doesn’t fit with my proposal above, as if the type an_iface is a type of dynamic size, as it cannot be returned (also, how does one specify the sendability bounds? They would have to be added as bounds to the type I, etc)