Skip to content

Ghost types #16

Open
Open
@HKalbasi

Description

@HKalbasi

Not sure if here is the right place, but there are some ideas for filling const generic params with runtime values in ZSTs, that enables dependent types for "proof as program" style things.

Ghost types

A type is ghost if it has no runtime data and effect. That is, a ZST annotated with a ghost keyword:

ghost struct Le<const A: usize, const B: usize>(());
// we control instantiating of this type in a way that it has instance iff A <= B

Why someone would annotate type with ghost? Because it will enable for its const generic parameters to depend on runtime values, so this will be accepted:

let x = 2;
let prf: Le<x, 5> = Le::<x, 5>(());

The x in Le<x, 5> will be considered as a const variable, and only unifies with itself. only immutable variables and function parameters are allowed (that types doesn't contain mutable reference or interior mutablity) to be in const generics of ghost types.

This allows to write function with dependent signature:

fn do_smth(x: usize, y: usize, x_le_y: Le<x, y>) {
  // we know x is lesser than y, so we can for example index an array without bound checking.
}

Which is the whole propose of ghost types. Benefits of powerful type system is clear, it can enable more correct and specified code, less unsafe code, less runtime checks, ... This kind of thing is actually the tool for formal verification in languages like Coq. I don't explain it more as you are probably already aware of.

Type checking

How we type check such function calls? It will reuse const generic infrastructures. For example that function will become roughly equivalent to this for type checking propose:

fn do_smth<const x: usize, const y: usize>(x_le_y: Le<x, y>);

And in call site, we consider each immutable binding as a const variable and each parameter as an abstract const expression, so in:

do_smth(f(a), g(2), prf);
  • f and g should be const fn
  • if we assume g(2) is 7, type of prf should unify with Le<f(a), 7>.

so this is actually different with:

let b = f(a);
do_smth(b, g(2), prf);

as here f can be anything and type of prf is Le<b, 7> in this case.

Code generation

After type checking, we replace all ghost types with (), so that function will become:

fn do_smth(x: usize, y: usize, x_le_y: ());

which is normal function today.

I have some extenstions like ghost fn in mind, but I think this is enough for starting discussion.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions