Browse the docs

silk.core.reflect.

Compile-time descriptions of the fields of a struct or tuple type, for generic code that visits each field.

When to use#

Use Reflect to write one generic function that works on the fields of many types, such as a serializer or a formatter. Use Reflect.fields with static for to visit each field, and Reflect.borrowField to read the value of a field at runtime, or Reflect.borrowFieldMut to change it.

Details#

Descriptors exist only during static evaluation. They have no runtime value, so reflection adds no runtime cost. The compiler expands a static for over Reflect.fields into one copy of the loop body for each field, in declaration order. In each copy, the field has its own type.

A descriptor shows only the fields that are visible from the module that calls the reflection operation. Private fields of a type from another module are not in the list.

Examples#

Add all fields of a struct#

silk
import silk.core.reflect { Reflect }

struct Pair {
  pub first: i32,
  pub second: i32,
}

fn sum(owner: &Pair) -> i32 {
  let mut total = 0
  static for field in Reflect.fields<Pair>() {
    total = total + Reflect.borrowField<Pair, i32>(owner, field).*
  }
  return total
}

pub fn main() -> i32 {
  let pair = Pair { first: 20, second: 22 }
  if sum(&pair) != 42 {
    return 1
  }
  return 0
}

Import as Reflect with import silk.core.reflect { Reflect }.

Public declarations: 11.

namedKind#

silk
pub const namedKind: u8

The Reflect.typeKind result for a declared struct with named fields.

positionalKind#

silk
pub const positionalKind: u8

The Reflect.typeKind result for a declared tuple type with positional fields.

anonymousNamedKind#

silk
pub const anonymousNamedKind: u8

The Reflect.typeKind result for an anonymous record with named fields.

anonymousPositionalKind#

silk
pub const anonymousPositionalKind: u8

The Reflect.typeKind result for an anonymous tuple with positional fields.

arrayKind#

silk
pub const arrayKind: u8

The Reflect.typeKind result for a fixed array, such as [i32; 3]. It has no fields.

otherKind#

silk
pub const otherKind: u8

The Reflect.typeKind result for every other type, such as i32, string, a slice, an enum, or a structural union A | B. It has no fields.

unionKind#

silk
pub const unionKind: u8

The Reflect.typeKind result for a nominal union, such as union Shape { ... }. It has no fields of its own; list its variants with Reflect.variants.

labeledFieldKind#

silk
pub const labeledFieldKind: u8

The Reflect.fieldKind result for a field with a name. Read the name with Reflect.fieldLabel.

positionalFieldKind#

silk
pub const positionalFieldKind: u8

The Reflect.fieldKind result for a field with a position. Read the position with Reflect.fieldOrdinal.

variantFieldKind#

silk
pub const variantFieldKind: u8

The Reflect.fieldKind result for a field of a union variant, from Reflect.variantFields. It has both a name, read with Reflect.fieldLabel, and a position in its variant, read with Reflect.fieldOrdinal.

Reflect#

silk
pub struct Reflect

The owner type of the reflection operations. It has no fields and is never constructed.

Details#

Each operation except Reflect.borrowField and Reflect.borrowFieldMut is static. It runs only during static evaluation, such as in a static for, a static if, or a static fn.

Implementation Reflect#

silk
impl Reflect

Associated function typeOf#

silk
pub static fn typeOf<Owner>() -> Intrinsic.Type<Owner>

Returns the compile-time descriptor of the struct or tuple type Owner.

Details#

Give the descriptor to Reflect.typeKind to find the kind of type.

Associated function fields#

silk
pub static fn fields<Owner>() -> Intrinsic.Fields<Owner>

Returns the compile-time descriptors of the fields of Owner, in declaration order.

Details#

Use the result as the sequence of a static for. Each element has the type Intrinsic.Field<Owner, Value>, where Value is the type of that field.

Gotchas#

The list contains only the pub fields, even when the caller is the module that declares Owner, because visibility is judged from silk.core.reflect. Call Intrinsic.reflectFields<Owner>() directly to list the fields that the calling module can see.

Associated function typeKind#

silk
pub static fn typeKind<Owner>(descriptor: Intrinsic.Type<Owner>) -> u8

Returns the kind of type that descriptor describes: namedKind, positionalKind, anonymousNamedKind, anonymousPositionalKind, arrayKind, or otherKind. Every type has a kind, so a generic function can branch on the kind of its type parameter.

Associated function fieldKind#

silk
pub static fn fieldKind<Owner, Value>(field: Intrinsic.Field<Owner, Value>) -> u8

Returns labeledFieldKind for a named field, positionalFieldKind for a positional field, or variantFieldKind for a field of a union variant.

Associated function fieldLabel#

silk
pub static fn fieldLabel<Owner, Value>(field: Intrinsic.Field<Owner, Value>) -> string<'static>

Returns the name of a named field, as written in the type declaration.

Gotchas#

Use this function only for a field where Reflect.fieldKind is labeledFieldKind or variantFieldKind.

Associated function fieldOrdinal#

silk
pub static fn fieldOrdinal<Owner, Value>(field: Intrinsic.Field<Owner, Value>) -> usize

Returns the zero-based position of a positional field.

Gotchas#

Use this function only for a field where Reflect.fieldKind is positionalFieldKind or variantFieldKind.

Associated function borrowField#

silk
pub fn borrowField<Owner, Value>(owner: &Owner, static field: Intrinsic.Field<Owner, Value>) -> &Value

Returns a shared borrow of the field of owner that field describes.

Details#

field is a static argument, so the compiler selects the field during specialization. At runtime this function is a plain field access. The borrow lives as long as the borrow of owner.

Gotchas#

field must come from Reflect.fields for the same Owner type. This function cannot move a field out of an owned value.

Associated function borrowFieldMut#

silk
pub fn borrowFieldMut<Owner, Value>(owner: &mut Owner, static field: Intrinsic.Field<Owner, Value>) -> &mut Value

Returns a mutable borrow of the field of owner that field describes.

Details#

field is a static argument, so the compiler selects the field during specialization. At runtime this function is a plain field access. The borrow lives as long as the mutable borrow of owner, and owner cannot be used while the field borrow is live.

Examples#

silk
import silk.core.reflect { Reflect }

struct Counters {
  pub hits: i32,
  pub misses: i32,
}

fn reset(counters: &mut Counters) -> () {
  static for field in Reflect.fields<Counters>() {
    let place = Reflect.borrowFieldMut<Counters, i32>(&mut counters.*, field)
    place.* = 0
  }
  return ()
}

pub fn main() -> i32 {
  let mut counters = Counters { hits: 4, misses: 2 }
  reset(&mut counters)
  return counters.hits + counters.misses
}

Gotchas#

field must come from Reflect.fields for the same Owner type. owner must be a mutable borrow; a shared borrow is refused.

Associated function variants#

silk
pub static fn variants<Union>() -> Intrinsic.Variants<Union>

Returns the compile-time descriptors of the variants of the nominal union Union, in declaration order.

Details#

Use the result as the sequence of a static for. Each element has the type Intrinsic.Variant<Union>. Give it to Reflect.variantLabel, Reflect.variantFields, Reflect.makeVariant or Reflect.isVariant.

Examples#

silk
import silk.core.option { Option }
import silk.core.reflect { Reflect }

union Command {
  Status,
  Clone { pub url: string, pub depth: u32 },
}

static fn fieldCount<U>(variant: Intrinsic.Variant<U>) -> usize {
  let mut count: usize = 0
  static for field in Reflect.variantFields(variant) { count = count + 1 }
  return count
}

// Builds the variant of `U` named `word`, if it has no fields.
fn unitNamed<U>(word: string) -> Option<U> {
  static for variant in Reflect.variants<U>() {
    static if fieldCount(variant) == 0 {
      if word == Reflect.variantLabel(variant) {
        return Option<U>.some(Reflect.makeVariant(variant, ()))
      }
    }
  }
  return Option<U>.none()
}

pub fn main() -> i32 {
  let found = unitNamed<Command>("Status")
  return match move found {
    Option<Command>.Some {value: Command.Status} => 0
    _ => 1
  }
}

Associated function variantLabel#

silk
pub static fn variantLabel<Union>(variant: Intrinsic.Variant<Union>) -> string<'static>

Returns the name of a variant, as written in the union declaration.

Associated function variantFields#

silk
pub static fn variantFields<Union>(variant: Intrinsic.Variant<Union>) -> Intrinsic.Fields<Union>

Returns the compile-time descriptors of the fields of a variant, in declaration order.

Details#

Each element has the type Intrinsic.Field<Union, Value>, where Value is the type of that field, and the kind variantFieldKind. A unit variant has none.

Gotchas#

The list contains only the pub fields, because visibility is judged from silk.core.reflect. A field descriptor of a variant cannot be given to Reflect.borrowField, because the union holds the field only while that variant is active.

Associated function makeVariant#

silk
pub fn makeVariant<Union, Fields>(static variant: Intrinsic.Variant<Union>, fields: Fields) -> Union

Returns the variant that variant describes, built from fields: a tuple of the variant's fields in declaration order, or () for a variant without fields.

Details#

variant is a static argument, so the compiler selects the variant during specialization. At runtime this function is a plain variant construction, such as Shape.Line {length: a, width: b} for makeVariant(line, (a, b)). The tuple is moved into the variant.

Examples#

silk
import silk.core.reflect { Reflect }

union Shape {
  Dot,
  Circle { pub radius: u32 },
  Line { pub length: u32, pub width: u32 },
}

fn area(shape: Shape) -> u32 {
  return match move shape {
    Shape.Dot => 0
    Shape.Circle {radius} => radius * radius * 3
    Shape.Line {length, width} => length * width
  }
}

static fn fieldCount<U>(variant: Intrinsic.Variant<U>) -> usize {
  let mut count: usize = 0
  static for field in Reflect.variantFields(variant) { count = count + 1 }
  return count
}

pub fn main() -> i32 {
  let two: u32 = 2
  let three: u32 = 3
  let four: u32 = 4
  let mut total: u32 = 0
  static for variant in Reflect.variants<Shape>() {
    let static count = fieldCount(variant)
    static if count == 0 { total = total + area(Reflect.makeVariant(variant, ())) }
    static if count == 1 { total = total + area(Reflect.makeVariant(variant, (two,))) }
    static if count == 2 { total = total + area(Reflect.makeVariant(variant, (three, four))) }
  }
  // 0 + 12 + 12
  if total != 24 {
    return 1
  }
  return 0
}

Gotchas#

The tuple must have exactly the variant's field types in declaration order. Every field of the variant must be pub, since the variant is built from silk.core.reflect.

Associated function isVariant#

silk
pub fn isVariant<Union>(owner: &Union, static variant: Intrinsic.Variant<Union>) -> bool

Returns whether owner holds the variant that variant describes.

Details#

variant is a static argument, so at runtime this function is a plain test of the union's active variant, like a match with one arm for that variant.

Category: Core.

Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2.

View the source of this page