Перейти к содержанию

Рефлексия(Reflection)

Рефлексия помогает писать код, который исследует собственную структуру на этапе компиляции и сообщает информацию о типах. Это делает возможным создание таких возможностей, как структурная валидация, автоматические сравнения, сериализация, более безопасные утверждения (assertions) и более информативные сообщения об ошибках без жёсткого кодирования деталей для конкретных реализаций типов.

Caution Рефлексия в Mojo была введена недавно и в настоящее время является неполной. Некоторые возможности рефлексии ограничены, нестабильны или ещё не полностью доступны через интерфейс языка. Эта страница описывает направление развития этой возможности, а также те части, которые доступны на сегодняшний день. Все примеры отражают состояние языка на момент публикации этой страницы(29 января 2026). Они могут измениться по мере развития поддержки рефлексии.

Почему рефлексия имеет значение

Рефлексия часто используется для сериализации, например, для преобразования значений в JSON или MessagePack независимо от типа. Она также поддерживает общие структурные задачи. Вместо того, чтобы вручную вводить логику равенства, хэширования или копирования, рефлексия может автоматически применять эти операции ко всем полям.

В Mojo рефлексия происходит исключительно во время компиляции. Компилятор использует информацию о типе для генерации специализированного кода, что позволяет избежать затрат времени выполнения и при этом сохранить гибкость кода.

Учитывая это, на этой странице мы рассмотрим рефлексию на примерах, чтобы вы могли увидеть рабочий код для изучения и использования. Код отражения намеренно параметризован и требует больших затрат времени на компиляцию. В приведенных ниже примерах показаны шаблоны, которые вы можете использовать в реальном коде, основанном на рефлексии.

Пример: Представление типа

В этом примере показано, как использовать рефлексию для проверки типа во время компиляции. В нем показано, как извлекать имя типа и выполнять итерацию по полям структуры, включая их имена и типы. Этот шаблон является основой для большинства утилит, основанных на рефлексии, таких как проверка подлинности, копирование и проверка на равенство.

Входные данные во время компиляции

API-интерфейсы рефлекции в Mojo запускаются во время компиляции и принимают входные данные во время компиляции.

Хотя рефлексия происходит во время компиляции, оно может влиять на поведение значений во время выполнения. Знание типа значения открывает путь к получению метаданных, относящихся к конкретному типу, таких как количество полей, имена и типы. Затем эти метаданные можно использовать для безопасного доступа к полям конкретных экземпляров и управления ими во время выполнения, при условии, что сам доступ определяется информацией во время компиляции.

Вызовы рефлекции

Вызовы рефлексии, используемые в следующем примере, включают в себя:

  • get_type_name[](): Возвращает имя типа.
  • struct_field_count[](), struct_field_names[]() и struct_field_types[](): Возвращают количество, имена и типы полей структуры.
from reflection import (
    struct_field_count, struct_field_names,
    get_type_name, struct_field_types
)

fn show_type[T: AnyType]():
    comptime type_name = get_type_name[T]()
    comptime field_count = struct_field_count[T]() # count of fields
    comptime field_names = struct_field_names[T]() # indexed list of field names
    comptime field_types = struct_field_types[T]() # indexed list of field types

    print("struct", type_name)

    @parameter
    for idx in range(field_count):
        comptime field_name = field_names[idx]
        comptime field_type = get_type_name[field_types[idx]]()
        var intro = "├──" if idx < (field_count - 1) else "└──"
        print(intro, " var ", field_name, ": ", field_type, sep="")

@fieldwise_init
struct MyStruct:
    var x: String
    var y: Optional[Int]

comptime DefaultItemCount = 10

struct ParameterizedStruct[T: Copyable, item_count: Int = DefaultItemCount](Copyable):
    var list: List[Self.T]
    fn __init__(out self):
       self.list = List[Self.T](capacity=Self.item_count)

fn main():
    show_type[MyStruct]()
    show_type[Optional[Float64]]()
    show_type[Dict[Int, String]]()
    show_type[ParameterizedStruct[String, item_count=5]]()

Выводы

  • При работе с рефлекией необходимо использовать параметризованные типы в квадратных скобках параметров для вызовов, использующих функции рефлексии, такие как show_type[](). Эти элементы могут быть конкретными типами, такими как MyStruct, или параметрами универсального типа, такими как T.
  • В этом примере используется параметр T: anyType для функции show_type[](). Это наименьшее ограничение, которое позволяет функции работать с любым типом, передаваемым в месте вызова.
  • Функции рефлексии сами по себе параметризуются. Это означает, что вы не вызываете функцию foo(). Вы вызываете функцию foo[T](), чтобы отразить тип.
  • Вся обработка отраженной информации происходит во время компиляции. По этой причине в этих примерах рефлексии используются @parameter for и @parameter if.
    • В этом примере @parameter for позволяет использовать индекс цикла на всех итерациях.
    • В последующих примерах показан @parameter if.

Использование sep="" в show_type позволяет оставить двоеточие вровень с именем поля.

Программный вывод

В следующем выводе показан результат вызова функции show_type[]() для трех разных типов в main(). Каждый вызов выводит представление компилятора о типе, включая его полностью разрешенное имя и структуру его полей:

struct reflect.MyStruct
├── var x: String
└── var y: std.collections.optional.Optional[Int]

struct std.collections.optional.Optional[SIMD[DType.float64, 1]]
└── var _value: std.utils.variant.Variant[<unprintable>]

struct std.collections.dict.Dict[Int, String, std.hashlib._ahash.
    AHasher[[0, 0, 0, 0] : SIMD[DType.uint64, 4]]]
├── var _len: Int
├── var _n_entries: Int
├── var _index: std.collections.dict._DictIndex
└── var _entries: List[std.collections.optional.Optional[std.collections.
    dict.DictEntry[Int, String, std.hashlib._ahash.
    AHasher[[0, 0, 0, 0] : SIMD[DType.uint64, 4]]]]]

struct show_type.ParameterizedStruct[String, 5]
└── var list: List[String]

При сравнении имён типов в местах вызова с именами, показанными здесь, имейте в виду, что этот вывод отражает то, как компилятор представляет тип. Это включает параметры со значениями по умолчанию, такие как Hasher у словаря. Псевдонимы типов времени компиляции разворачиваются, поэтому Float64 отображается как SIMD[DType.float64, 1].

В этом примере было показано, как читать информацию о типах. В следующих примерах показано, как использовать эту информацию для манипулирования экземплярами структур.

Пример: Копирование данных

Этот пример показывает, как отражённая информация о полях может определять реальное поведение программы. Он использует рефлексию для копирования данных из одного экземпляра в другой путём перебора полей и проверки, какие из них безопасно копировать. Этот шаблон демонстрирует, как рефлексия позволяет реализовывать обобщённые, типобезопасные операции без жёсткого прописывания доступа к полям.

Когда структура реализует MakeCopyable, она получает метод copy_to(), который использует рефлексию для выполнения копирования. Как и все методы, его вызывают у экземпляра. Для этого метода необходимо передать целевой экземпляр.

По своему смыслу его поведение похоже на ImplicitlyCopyable, но copy_to() ограничивает копирование только теми полями, типы которых реализуют Copyable. Он требует уже инициализированный целевой объект, что позволяет избежать сопоставления значений с аргументами __init__.

from reflection import struct_field_count, struct_field_types

trait MakeCopyable:
    fn copy_to(self, mut other: Self):
        comptime field_count = struct_field_count[Self]()
        comptime field_types = struct_field_types[Self]()

        @parameter
        for idx in range(field_count):
            comptime field_type = field_types[idx]

            # Guard: field type must be copyable
            @parameter
            if not conforms_to(field_type, Copyable): continue

            # Perform the copy
            ref p_value = __struct_field_ref(idx, self)
            __struct_field_ref(idx, other) = p_value

Выводы

  • Функция выполняет итерацию по отраженным полям и проверяет каждое из них на соответствие Copyable, пропуская любое поле, которое не может быть скопировано.
  • В качестве метода copy_to() не требуется параметр типа, такой как copy_to[T](). Он имеет прямой доступ к Self, который включается с помощью трейта MakeCopyable.
  • Реализация copy_to() сильно параметризована и оценивается во время компиляции. Он использует @parameter для и @parameter if вместе с вызовами отражения.
  • Вызовы __struct_field_ref(idx, self) и __struct_field_ref(idx, other) возвращают ссылки на поля по индексу, что позволяет выполнять как чтение из исходного экземпляра, так и запись в целевой экземпляр.

Создание структуры

Следующая многотипная структура соответствует MakeCopyable, что означает, что вы можете вызывать copy_to для ее экземпляров:

@fieldwise_init
struct MultiType(Writable, MakeCopyable):
    var w: String
    var x: Int
    var y: Bool
    var z: Float64

    fn write_to[W: Writer](self, mut writer: W):
        writer.write("[{}, {}, {}, {}]".format(self.w, self.x, self.y, self.z))

Использование функциональности копирования

Демонстрируя поведение, эта функция main() создаёт два экземпляра: один заполненный обычными значениями, а другой — фактически обнулённый. После копирования target_instance получает значения из полей original_instance:

fn main():
    var original_instance = MultiType("Hello", 1, True, 2.5)
    var target_instance = MultiType("", 0, False, 0.0)

    print("Original instance:", original_instance)     # "Hello", 1, True, 2.5
    print("Target instance before: ", target_instance) # "", 0, False, 0.0

    original_instance.copy_to(target_instance)
    print("Target instance after: ", target_instance)  #  "Hello", 1, True, 2.5

Пример: Проверка равенства

Этот пример демонстрирует, как рефлексия может использоваться для реализации структурного равенства. Разворачивание циклов на этапе компиляции, проверки соответствия интерфейсам и рефлексия управляют сравнениями во время выполнения. Это тот же шаблон, который используется для обобщённой проверки равенства, хеширования и логики валидации.

На каждой итерации test_equality проверяет соответствие Equatable и получает ссылки на значения полей из аргументов lhs и rhs. При первом неравенстве используется ранний выход (возврат False), в противном случае возвращается True:

from reflection import struct_field_count, struct_field_types

fn test_equality[T: AnyType](lhs: T, rhs: T) -> Bool:
    comptime field_count = struct_field_count[T]()
    comptime field_types = struct_field_types[T]()

    @parameter
    for idx in range(field_count):
        # Guard: field type must be equatable
        comptime field_type = field_types[idx]
        @parameter
        if not conforms_to(field_type, Equatable): continue

        # Fetch values
        ref lhs_value = __struct_field_ref(idx, lhs)
        ref rhs_value = __struct_field_ref(idx, rhs)

        # Early exit with `False` when inequality found
        if trait_downcast[Equatable](lhs_value) != trait_downcast[Equatable](rhs_value): return False

    return True

Выводы

  • В этом примере для обеспечения бесперебойной работы используются два ключевых элемента: conforms_to() и trait_downcast[]().
  • Функция conforms_to() гарантирует, что тип каждого поля является Equatable, и, следовательно, работает с оператором неравенства (!=).
  • trait_downcast используется с типами входных параметров. Он повторно привязывает введенное значение, возвращая значение, соответствующее указанному трейту. Если его тип после разрешения не соответствует этому трейту, это приведет к ошибке компиляции. Проверка соответствия в первую очередь гарантирует, что ошибка не возникнет.
  • Как следует из названия, функция __struct_field_ref() может использоваться только со структурными типами.

Вызов тестов

Чтобы продемонстрировать эту функцию, следующая функция main() сначала копирует значения (equal), а затем изменяет original_instance и снова проверяет (inequal):

fn main():
    var original_instance = MultiType("Hello", 1, True, 2.5)
    var target_instance = MultiType("", 0, False, 0.0)
    original_instance.copy_to(target_instance)
    print("Values equal" if \
        test_equality(original_instance, target_instance) \
        else "Values not equal") # Values equal


    original_instance.z = 42.0
    print("Values equal" if \
        test_equality(original_instance, target_instance) \
        else "Values not equal") # Values not equal

Learn more

  • Ознакомьтесь с пакетом документации, чтобы ознакомиться с дополнительными возможностями рефлекции в Mojo.
  • Узнайте больше о трейтах.
  • (Скоро появится): Ознакомьтесь с дженериками, тестами соответствия трейтам с помощью conforms_to() и обратными трансляциями с помощью trait_downcast[]().