Enum на языке Rust

Почему size_of::<demo>() == 48

enum demo{
    a(String),
    b(String, String)
}

а с другой стороны size_of::<demo2>() == 56

enum demo2{
    a(String),
    b(String, String),
    c(i8)
}

Меня смущают эти два примера. Разве они не должны быть одного размера?

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

mkrieger1 07.08.2023 12:21

Может быть, компилятор не знает, как оптимизировать третье значение ниши: A и B можно отличить по тому, что вторая половина равна нулю (или, по крайней мере, второй указатель String равен нулю), это в основном упрощает до (String, Option<String>) поэтому может срабатывать NVO на опциональном типе. Когда вы добавляете третий вариант, нет ниши с тремя значениями, которую может использовать компилятор, вручную вы можете «разделить» значение ниши, но я не думаю, что у компилятора есть знания для этого. Таким образом, компилятору нужен явный байт тега.

Masklinn 07.08.2023 12:24

поскольку перечисление принимает максимальный размер любого значения во всех возможных значениях. Таким образом, в обоих случаях максимальный размер равен (String, String), что равно 48, так почему же второй дает размер 56?

Shivam Varshney 07.08.2023 12:25

Не совсем он также должен различать варианты, поэтому без оптимизации ниши должен быть дискриминант, дополненный выравниванием содержащихся значений, так что это 8 (discriminant) + 2*24 (values) = 56 байтов для неоптимизированного demo2, но только 2*24 (values) = 48 для demo, где он нашел нишу.

cafce25 07.08.2023 12:28

Что такое НВО? не могли бы вы поделиться некоторыми подробностями об этом. @cafce25

Shivam Varshney 07.08.2023 13:21
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
1
5
68
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

String внутренне представлен как структура с тремя значениями:

  • Ненулевой указатель на байты строки.
  • Длина строки в байтах в виде usize.
  • Выделенное пространство блока памяти, как usize.

В 64-битной системе каждому из них требуется 8 байтов, так что всего 3 * 8 = 24 bytes.

Если у вас их два, то 2 * 24 = 48 bytes. Это длина вашего типа demo.

Но где компилятор хранит, будь то demo::a или demo::b? Вот где в игру вступает NVO или оптимизация ценности ниши. Хитрость в том, что указатель внутри строки является указателем NonNull, который не может иметь значение null, даже если строка не распределена.

Это невозможное значение называется нишевым значением и может использоваться компилятором, чтобы указать, какое значение перечисления действительно существует. В вашем конкретном случае при сохранении demo::a он может установить память, используемую для указателя второй строки, на null. И, очевидно, когда он сохранит demo::b, он будет ненулевым.

Что касается вашего второго примера demo2, он имеет три значения перечисления, поэтому для хранения дискриминанта требуется как минимум 2 бита. Он мог бы использовать один из указателей String для одного бита, а другой для другого бита, но, насколько мне известно, компилятор не будет собирать биты из разных ниш для выполнения NVO, так что это не годится.

А если с demo2 у вас нет NVO, то нужно 24 байта на каждую строку плюс 1 байт на дискриминант перечисления, то есть 2 * 24 + 1 = 49... но мы забываем о выравнивании! В Rust, как и в C, размер типа должен быть кратен его выравниванию (это необходимо, например, для построения массивов). Выравнивание demo2 является наибольшим выравниванием любого из его членов, а выравнивание 64-битного указателя обычно равно 8. Наименьшее кратное 8, большее или равное 49, равно 56, так что это размер, который вы получаете.

если использование нулевого значения решает проблему, почему это требует дополнительного дискриминанта? enum demo3{ a(String), b(String, String), c(String, String,String) } он дает размер 80 байт, почему он не использует для этого значение NULL?

Shivam Varshney 08.08.2023 06:51

Указатель NonNull имеет только один бит значения ниши: либо он нулевой, либо нет, один бит ниши позволяет вам сделать два дискриминанта, если у вас есть три значения перечисления, то этого недостаточно, и компилятор должен хранить дискриминант как собственное значение.

rodrigo 08.08.2023 09:47

Другие вопросы по теме