Являются ли операторы в ядре действительно определенными круговыми?

Мы можем реализовать черты в core::ops для определения поведения операторов для наших типов. Сами черты аннотируются с атрибутами #[lang =...] поэтому компилятор знает, какие черты и операторы принадлежат друг другу.

Например, реализация Add для примитивных типов выглядит так (макрос вручную расширяется и упрощается отсюда):

impl Add for i32 {
    type Output = i32;

    fn add(self, other: i32) -> i32 {
        self + other
    }
}

К моему удивлению, реализация использует внутренний оператор +, который предположительно вызывает self.add(other), что приводит к бесконечной рекурсии. Очевидно, что все происходит не так, потому что выражения вроде 3 + 4 (при отсутствии постоянной сгибания) работают отлично.

Теперь рассмотрим эту наивную реализацию функции Add:

use std::ops::Add;

struct Foo;

impl Add for Foo {
    type Output = Foo;

    fn add(self, other: Foo) -> Foo {
        self + other
    }
}

fn main() {
    let two_foo = Foo + Foo;
}

Компилятор предупреждает, что function cannot return without recurring и запуск этой программы в режиме отладки должным образом останавливается с fatal runtime error: stack overflow.

Как компилятор знает, как добавить два числа, не попадая в рекурсивную лазейку?

Ответ 1

Как компилятор знает, чтобы добавить два числа, не попадая в рекурсивную лазейку?

Поскольку компилятор является компилятором, а компилятор знает, что ему не нужна реализация Add для добавления двух чисел. Если он делает постоянную складку, он просто добавляет их. Если он генерирует код, он сообщает LLVM добавлять их во время выполнения.

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

Иначе говоря: Add - это то, что использует компилятор, когда он иначе не знает, как добавить вещи. Но он уже знает, как добавлять числа, поэтому они им не нужны. Они обеспечены согласованностью с другими типами.

Ответ 2

Реализации Add которые полагаются на оператор сложения + в конце должны указывать на операции с примитивами (например, целые числа), а арифметические операции с ними реализуются с использованием встроенных компиляторов.

Более того, сами примитивы также являются встроенными компиляторами - обратите внимание, что вы не сможете найти их источники в документации std.

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