Что означает, что постоянное значение в Rust должно быть встроено?

#rust #constants #inline

#Ржавчина #константы #встроенное

Вопрос:

Документация для const :

Константы сохраняются в течение всего срока службы программы. Более конкретно, константы в Rust не имеют фиксированного адреса в памяти. Это потому, что они эффективно встроены в каждое место, в котором они используются. По этой причине ссылки на одну и ту же константу не обязательно гарантированно ссылаются на один и тот же адрес памяти.

Я видел только «встроенные функции» в C , но никогда не встроенные постоянные значения. Каково понятное для начинающих объяснение того, как это работает?

Меня также смущает «отсутствие фиксированного адреса в памяти». Означает ли это, что каждый раз, когда мы используем const значение, значение в стеке выделяется только для этого выражения, и после завершения выполнения выражения оно будет уничтожено?

Ответ №1:

Я видел только «встроенные функции» в C , но никогда не встроенные постоянные значения.

Ближайшим приближением к a const в Rust является enum в C .

Каково понятное для начинающих объяснение того, как это работает?

Простое объяснение для начинающих таково: это просто работает, не беспокойтесь о мельчайших деталях.

Меня также смущает «отсутствие фиксированного адреса в памяти». Означает ли это, что каждый раз, когда мы используем const значение, значение в стеке выделяется только для этого выражения, и после завершения выполнения выражения оно будет уничтожено?

ДА. Возможно. Нет.

Это означает именно то, что написано на tin: гарантия не предоставляется. Это оставляет компилятору максимальную свободу для оптимизации.


Хорошо, это все хорошо, но… что происходит на самом деле?

На практике существуют две ситуации:

  • значение достаточно простое: оно даже не касается стека, а вместо этого жестко закодировано непосредственно в сборке. Это, скорее всего, произойдет, например, для целых чисел.
  • значение не так просто: оно создается в памяти, доступной только для чтения, и ссылается / копируется оттуда. Несколько копий в стеке будут иметь разные адреса.

Что означает simple? Ну, это зависит. Для каждого сайта вызова компилятор может решить «достаточно просто» или нет, где оно близко к встраиванию.

Означает ли это, что каждый раз, когда мы используем const значение, значение в стеке выделяется только для этого выражения, и после завершения выполнения выражения оно будет уничтожено?

Оно не будет уничтожено. const переменные не могут иметь тип, который реализует Drop . Значение просто забывается, когда оно больше не используется. Если оно когда-либо занимало память в стеке, эта память будет перезаписана через некоторое время.

Комментарии:

1. Можете ли вы объяснить, почему C enum является наиболее близким?

2. @PavelStrakhov: enum значение в C — это именованное значение без адреса памяти (если у вас есть, enum { X }; тогда amp;X возникнет ошибка времени компиляции), а значение перечисления встроено в каждый сайт использования. Это немного ближе, чем #define потому что оно вычисляется только один раз (и это также намного чище: учитывает области видимости и т.д. ).

3. Вы говорите так, как будто компилятор может решить преобразовать его в static , чтобы избежать копий, но так ли это на самом деле? Кажется, я не могу заставить компилятор сделать это; он всегда выдает код, эквивалентный встраиванию выражения, даже если оно явно неоптимально.

4. @Veedrac: Я думал, что rustc в этом отношении ведет себя как Clang, но мои воспоминания могут сыграть со мной злую шутку, тем более, что я слишком много думал о том, const fn и какие значения могут в конечном итоге храниться в const (например, String , HashMap но не Arc или RefCell , потому что внутренняя изменчивость предотвращает хранение в ПЗУ). Я полагаю, что это еще один пример того, как работа переносится в LLVM:x

5. @Veedrac: Я проверил несколько вариантов на игровой площадке, и действительно, rustc выдает определение, встроенное в IR. Конечно, в Release LLVM просто постоянно сворачивает его, и вы получаете целое число непосредственно в регистре.

Ответ №2:

const N: i32 = 5 в Rust это похоже #define N 5 на C или C , выполненное с безопасностью типов.

Вы можете думать об этом как о текстовой замене, когда тип совпадает, то есть let foo = 32 N; эквивалентен let foo = 32 5; в вашем примере.

Комментарии:

1. Я бы предпочел сравнить его с enum , чем с #define , поскольку enum у него (немного) больше безопасности типов.

Ответ №3:

const Ant вообще не ведет себя как обычная переменная; когда вы определяете ее, она даже не получает свою собственную область видимости для целей проверки заимствования.

Сравните MIR, сгенерированный следующими фрагментами кода:

 fn main() {
    const N: i32 = 5;
}
  

и

 fn main() {
    let n: i32 = 5;
}
  

И вы обнаружите, что то, что N расширяется, больше похоже на функцию, чем на переменную:

 const main::N: i32 = {
    let mut _0: i32;                     // return pointer

    bb0: {
        _0 = const 5i32;                 // scope 0 at <anon>:2:20: 2:21
        return;                          // scope 0 at <anon>:2:5: 2:22
    }
}
  

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

Редактировать: по крайней мере, это то, что происходит на уровне MIR. Я не специалист по низкоуровневой оптимизации, поэтому фактический результат (действительно ли использовалась память стека) должен быть проверен в LLVM или даже ASM. Обратите внимание, что это также применимо к обычным переменным.