3.2 KiB
Compile time reflection
$ is used as a prefix for compile time (also referred to as 'comptime') operations.
Having built-in JSON support is nice, but V also allows you to create efficient serializers for any data format. V has compile time if and for constructs:
.fields You can iterate over struct fields using .fields, it also works with generic types (e.g. T.fields) and generic arguments (e.g. param.fields where fn gen[T](param T) {).
struct User { name string age int }
fn main() { $for field in User.fields { $if field.typ is string { println('${field.name} is of type string') } } }
// Output: // name is of type string .values You can read Enum values and their attributes.
enum Color { red @[RED] // first attribute blue @[BLUE] // second attribute }
fn main() { $for e in Color.values { println(e.name) println(e.attrs) } }
// Output: // red // ['RED'] // blue // ['BLUE'] .attributes You can read Struct attributes.
@[COLOR] struct Foo { a int }
fn main() { $for e in Foo.attributes { println(e) } }
// Output: // StructAttribute{ // name: 'COLOR' // has_arg: false // arg: '' // kind: plain // } .variants You can read variant types from Sum type.
type MySum = int | string
fn main() { $for v in MySum.variants { $if v.typ is int { println('has int type') } $else $if v.typ is string { println('has string type') } } }
// Output: // has int type // has string type .methods You can retrieve information about struct methods.
struct Foo { }
fn (f Foo) test() int { return 123 }
fn (f Foo) test2() string { return 'foo' }
fn main() { foo := Foo{} $for m in Foo.methods { $if m.return_type is int { print('${m.name} returns int: ') println(foo.$method()) } $else $if m.return_type is string { print('${m.name} returns string: ') println(foo.$method()) } } }
// Output: // test returns int: 123 // test2 returns string: foo .params You can retrieve information about struct method params.
struct Test { }
fn (t Test) foo(arg1 int, arg2 string) { }
fn main() { $for m in Test.methods { $for param in m.params { println('${typeof(param.typ).name}: ${param.name}') } } }
// Output: // int: arg1 // string: arg2
Example
// An example deserializer implementation
struct User {
name string
age int
}
fn main() {
data := 'name=Alice\nage=18'
user := decode[User](data)
println(user)
}
fn decode[T](data string) T {
mut result := T{}
// compile-time `for` loop
// T.fields gives an array of a field metadata type
$for field in T.fields {
$if field.typ is string {
// $(string_expr) produces an identifier
result.$(field.name) = get_string(data, field.name)
} $else $if field.typ is int {
result.$(field.name) = get_int(data, field.name)
}
}
return result
}
fn get_string(data string, field_name string) string {
for line in data.split_into_lines() {
key_val := line.split('=')
if key_val[0] == field_name {
return key_val[1]
}
}
return ''
}
fn get_int(data string, field string) int {
return get_string(data, field).int()
}
// `decode<User>` generates:
// fn decode_User(data string) User {
// mut result := User{}
// result.name = get_string(data, 'name')
// result.age = get_int(data, 'age')
// return result
// }