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 {
}Function with ct modifier will can be executed only in compile time.
cf fn foo() { ... }
fn bar() { foo(); } // illegal
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")
}
}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()
}
}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
}cf fn unsigned_type(size: usize) -> Type {
if size == 4 {
u32
} else if s == 8 {
u64
} else {
panic!()
}
}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;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
}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()
}
}#[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);
// ...
}
}