Проверка во время компиляции?
Есть ли лучший способ сделать это, или эта проверка во время выполнения лучше всего поддерживает Rust 1.0?
В общем, есть несколько хитрых решений для выполнения какого-либо тестирования произвольных условий во время компиляции. Например, есть static_assertions
crate, который предлагает несколько полезных макросов (включая один макрос, похожий на макрос static_assert
в C ++) . Однако это взломано и очень ограничено. .
В вашей конкретной ситуации я не нашел способа выполнить проверку во время компиляции. Основная проблема здесь в том, что нельзя использовать mem::size_of
или mem::transmute
для общего типа. Связанные вопросы: # 43408 и # 47966. По этой причине ящик static_assertions
тоже не работает.
Если задуматься, это также допускает ошибку, совершенно незнакомую программистам на Rust: ошибку при создании экземпляра универсальной функции с определенным типом. Это хорошо известно программистам на C ++ - границы свойств Rust используются для исправления часто очень плохих и бесполезных сообщений об ошибках. В мире Rust вам нужно будет указать ваше требование как привязку к трейту: что-то вроде where size_of::<T> == size_of::<usize>()
.
Однако в настоящее время это невозможно. Когда-то существовал довольно известный RFC "зависимой от константы системы типов", который разрешить такие ограничения, но пока отклонены. Поддержка таких функций медленно, но неуклонно развивается. Некоторое время назад "Miri" была добавлена в компилятор, что сделало возможным более мощное вычисление констант. Это средство для многих вещей, включая RFC "Const Generics", который был фактически объединен. Он еще не реализован, но ожидается, что он появится в 2018 или 2019 году.
К сожалению, он по-прежнему не обеспечивает нужного вам вида привязки. Сравнивая два константных выражения на равенство, были намеренно исключены из основного RFC для решения в будущих RFC.
Таким образом, следует ожидать, что в конечном итоге станет возможным ограничение, подобное where size_of::<T> == size_of::<usize>()
. Но в ближайшее время этого ожидать не стоит!
Обходной путь
В вашей ситуации я бы, вероятно, ввел небезопасный признак AsBigAsUsize
. Чтобы реализовать это, вы можете написать макрос impl_as_big_as_usize
, который выполняет проверку размера и реализует признак. Может быть, примерно так:
unsafe trait AsBigAsUsize: Sized {
const _DUMMY: [(); 0];
}
macro_rules! impl_as_big_as_usize {
($type:ty) => {
unsafe impl AsBigAsUsize for $type {
const _DUMMY: [(); 0] =
[(); (mem::size_of::<$type>() == mem::size_of::<usize>()) as usize];
// We should probably also check the alignment!
}
}
}
Здесь используются те же уловки, что и static_assertions
. Это работает, потому что мы никогда не используем size_of
для универсального типа, а только для конкретных типов вызова макроса.
Итак ... это явно далеко не идеально. Пользователь вашей библиотеки должен вызывать impl_as_big_as_usize
один раз для каждого типа, который он хочет использовать в вашей структуре данных. Но, по крайней мере, это безопасно: до тех пор, пока программисты используют макрос только для имплантации признака, признак фактически реализуется только для типов, имеющих тот же размер, что и usize
. Кроме того, очень понятна ошибка «trait bound AsBigAsUsize
is notailed».
А как насчет проверки во время выполнения?
Как сказано в комментариях, в вашем assert!
коде нет проверки во время выполнения, потому что оптимизатор постоянно свертывает проверку. Давайте протестируем этот оператор с помощью этого кода:
#![feature(asm)]
fn main() {
foo(3u64);
foo(true);
}
#[inline(never)]
fn foo<T>(t: T) {
use std::mem::size_of;
unsafe { asm!("" : : "r"(&t)) }; // black box
assert!(size_of::<usize>() == size_of::<T>());
unsafe { asm!("" : : "r"(&t)) }; // black box
}
Сумасшедшие asm!()
выражения служат двум целям:
- «Скрытие»
t
от LLVM, так что LLVM не может выполнять оптимизацию, которую мы не хотим (например, удаление всей функции)
- отмечая определенные места в результирующем коде ASM, который мы будем рассматривать
Скомпилируйте его ночным компилятором (в 64-битной среде!):
rustc -O --emit=asm test.rs
Как обычно, полученный ассемблерный код трудно читать; вот важные места (с некоторой очисткой):
_ZN4test4main17he67e990f1745b02cE: # main()
subq $40, %rsp
callq _ZN4test3foo17hc593d7aa7187abe3E
callq _ZN4test3foo17h40b6a7d0419c9482E
ud2
_ZN4test3foo17h40b6a7d0419c9482E: # foo<bool>()
subq $40, %rsp
movb $1, 39(%rsp)
leaq 39(%rsp), %rax
#APP
#NO_APP
callq _ZN3std9panicking11begin_panic17h0914615a412ba184E
ud2
_ZN4test3foo17hc593d7aa7187abe3E: # foo<u64>()
pushq %rax
movq $3, (%rsp)
leaq (%rsp), %rax
#APP
#NO_APP
#APP
#NO_APP
popq %rax
retq
Пара _22 _-_ 23_ является нашим asm!()
выражением.
- Случай
foo<bool>
: вы можете видеть, что наша первая asm!()
инструкция скомпилирована, затем делается безусловный вызов panic!()
и после этого ничего не происходит (ud2
просто говорит, что "программа никогда не сможет достичь этого места, panic!()
расходится").
- Случай
foo<u64>
: вы можете видеть обе пары _31 _-_ 32_ (оба asm!()
выражения) без чего-либо между ними.
Так что да: компилятор полностью снимает проверку.
Было бы лучше, если бы компилятор просто отказался компилировать код. Но таким образом мы по крайней мере знаем, что нет накладных расходов во время выполнения.
person
Lukas Kalbertodt
schedule
27.09.2016
val
и сохраняет его в v. О, и если вы хотите сохранить указатель, сохраните указатель на что-то, что действительно существует - например, указатель наT
вBox<T>
. - person bluss   schedule 20.05.2015val
, если типval
точно соответствует размеру слова. Целью здесь является взаимодействие FFI с существующим кодом C с использованием данных, хранящихся таким образом. - person llasram   schedule 20.05.2015T
в целом. Однако assert, конечно, мономорфизируется и компилируется во время компиляции, поэтому, по крайней мере, нет накладных расходов. - person bluss   schedule 20.05.2015assert!
разрешения на бездействие илиpanic!
во время компиляции. Если такая проверка во время выполнения на самом деле лучшее, что может сделать Rust 1.0, я приму это как ответ! - person llasram   schedule 20.05.2015#[test]
, которые содержат этиassert!
. - person porglezomp   schedule 03.09.2015