Skip to content

Instantly share code, notes, and snippets.

@stepancheg
Last active September 7, 2018 00:39
Show Gist options
  • Select an option

  • Save stepancheg/7c440fa44c242e299e47c9ae0f7263a7 to your computer and use it in GitHub Desktop.

Select an option

Save stepancheg/7c440fa44c242e299e47c9ae0f7263a7 to your computer and use it in GitHub Desktop.

Here below there is a couple of thoughts about metaprogramming in Rust, the area of compile-time reflection.

Is it not a proposal, just an idea of what I would like to have.

There are several problems which are not solvable in modern rust, except with programmatic macros (e. g. serde).

  • automatic to and from JSON mapping
  • automatic database mapping (ORM)
  • binary struct serializers
  • custom fmt::Debug derive which skips fields which are not formattable
  • runtime reflection generator (print a struct field by a field name)

Example: database mapping:

enum DatabaseValue {
  Integer(i64),
  String(String),
}

struct MyData {
}

Possible language changes

cf functions

Function with ct modifier will can be executed only in compile time.

cf fn foo() { ... }

fn bar() { foo(); } // illegal

compile-time if-type expression

fn any_to_string<T>(t: &T) -> String {
    ct if <T: Display> {
        // this typechecks because inside if statement T: Display
        format!("{}", t)
    } ct else if <T == String> {
        // T is String here
        t
    } ct else if <U: T == Option<U>, U: Display> {
        // T is a vector, and new type variable U is available here
        if let Some(v) = t {
            format!("some {}", v)
        } else {
            format!("none")
        }
    } ct else {
        format!("not Display")
    }
}

instantiation failure intrinsic

fn any_to_string<T>(t: &T) -> String {
    ct if <T: Display> {
        format!("{}", t)
    } ct else {
        // function instantiation fails if T is not Display
        intrinsics::insta_failure()
    }
}

Make [] and str available in const context

const fn make_some_numbers() -> [u32] {
    [10, 20, 30]
}

const fn make_a_number() -> u32 {
    let mut sum: u32 = 0;
    for v in make_some_numbers() {
        sum += v;
    }
    sum
}

Make a Type type available in const context

cf fn unsigned_type(size: usize) -> Type {
    if size == 4 {
        u32
    } else if s == 8 {
        u64
    } else {
        panic!()
    }
}

Introspection intrinsics

mod intrinsics;

enum TypeKind {
    Struct,
    Enum,
    Other,
}

const fn type_kind<T>() -> TypeKind;
// a list of struct field names
cf fn struct_fields<T>() -> [str];
// a list of struct variants
cf fn enum_variants<T>() -> [str];

ct fn struct_field_type(t: Type, name: str) -> Type;

fn set_struct_field<T, U, const name: str>(s: &mut T, value: T);
fn get_struct_field<T, U, const name: str>(s: &T) -> &U;

Example

Database mapper

enum Cell {
    Int(i64),
    String(String),
}

fn to_cell<V>(value: &V) -> Cell {
    cf if <V == String> {
        Cell::String(v.clone())
    } ct else if <V == i64> {
        Cell::Int(v)
    } ct else {
        instantiation_panic()
    }
}

fn to_db_row<T>(t: &T) -> Vec<(&'static str, Cell)> {
    let mut r = Vec::new();
    cf for field_name in struct_field_names<T>() {
        let value = struct_get_field::<_, _, field_name>(t);
        r.push((&field_name, to_cell(value)));
    }
    r
}

Debug any object

fn debug_anything<T>(t: &T, f: &mut fmt::Formatter) {
    cf if <T: Debug> {
        Debug::fmt(t, f)
    } else {
        let mut debug_struct = f.debug_struct(&struct_name::<T>());
        ct for field_name in struct_field_names::<T>() {
            // declare new type variable `U`
            <U> let field: &U = struct_get_field::<_, _, field_name>(t);
            ct if <U: Debug> {
                debug_struct.field(&field_name, field);
            }
        }
        debug_struct.finish()
    }
}

Custom serializer with annotations

#[ser::enable]
struct MyExampleStruct {
    #[ser::align = 8]
    weigth: u32,
    #[ser::mode = "varint"]
    height: u32,
}

fn serialize_with_my_serializer(t: &T, w: &mut Write) {
    ct if let None = struct_get_annotation<T>("ser::enable") {
        instantiation_fail();
    }
    
    cf for field_name in struct_field_names::<T>() {
        let align = struct_field_get_annotation::<T, field_name>("ser::align").unwrap_or(0);
        // or effectively the same
        ct let align = struct_field_get_annotation::<T, field_name>("ser::align").unwrap_or(0);
        let field = struct_field_get::<T, field_name(t);
        // ...
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment