提问人:xjn xjn 提问时间:10/19/2023 最后编辑:cafce25xjn xjn 更新时间:10/19/2023 访问量:78
在编译时限制参数值
Limit parameter values at compile time
问:
Rust 中有没有办法根据泛型类型将参数值限制为范围或条件?
给定为无符号,或T
u8
u16
const fn do_something_with_a_bit_offset<T>(offset: u8) -> T {
// .. some ops return a T ..
}
如果是,偏移量可以是0..7,偏移量>7不应该编译
如果是,偏移量可以是0..15,偏移量>15不应该编译T
u8
T
u16
let mask = do_something_with_a_bit_offset::<u8>(0); // OK
let mask = do_something_with_a_bit_offset::<u8>(8); // Compile error
let mask = do_something_with_a_bit_offset::<u16>(0); // OK
let mask = do_something_with_a_bit_offset::<u16>(8); // OK
let mask = do_something_with_a_bit_offset::<u16>(16); // Compile error
如何在编译时执行此操作?
答:
在开始之前,我必须提到一个显而易见的事实:如果不是编译时常量,则无法避免运行时检查。offset
如果参数保证为编译时常量,则可以使用 const 泛型和一些相关的常量魔术:offset
trait CheckOffset<const OFFSET: usize> {
const CHECKED_OFFSET: usize;
}
impl<T: MaxOffset, const OFFSET: usize> CheckOffset<OFFSET> for T {
const CHECKED_OFFSET: usize = if OFFSET <= T::MAX_OFFSET {
OFFSET
} else {
panic!("Invalid offset") // Compile time panic
};
}
CheckOffset
是一个帮助程序特征,它定义了一个关联的常量,该常量(在其实现中)实际上执行偏移量检查。仅当给定的偏移量小于 () 的最大偏移量时,才会编译对 的引用。 是一个帮助程序特征,用于定义类型的最大位偏移量:CHECKED_OFFSET
<T as CheckOffset<OFFSET>>::CHECKED_OFFSET
OFFSET
T
<T as MaxOffset>::MAX_OFFSET
MaxOffset
trait MaxOffset {
const MAX_OFFSET: usize;
}
impl<T> MaxOffset for T {
const MAX_OFFSET: usize = 8 * size_of::<T>() - 1;
}
如果您需要一个函数来获取给定类型的编译时检查偏移量,则必须T
- 添加一个 const 泛型参数 () 来表示输入偏移量
OFFSET
- 向类型添加约束
CheckOffset<OFFSET>
T
- 用作实际偏移量。如果不使用常量,则不会计算关联常量实现中的最大偏移量检查。
<T as CheckOffset<OFFSET>>::CHECKED_OFFSET
CHECKED_OFFSET
fn foo<T: CheckOffset<OFFSET> + 'static, const OFFSET: usize>() {
let offset = T::CHECKED_OFFSET;
println!("{} Offset: {:?}", type_name::<T>(), offset);
}
fn main() {
foo::<u8, 0>();
foo::<u8, 7>();
// foo::<u8, 8>(); // Compile error
foo::<u16, 0>();
foo::<u16, 15>();
// foo::<u16, 16>(); // Compile error
foo::<u32, 0>();
foo::<u32, 31>();
// foo::<u32, 32>(); // Compile error
}
此方法适用于您可能具有的任何编译时常量可计算约束。
如果是运行时常量,则无法在所有情况下避免检查,但可以将验证它的责任从函数解除到调用方。这样做的优点是:失败是清楚地传达的(通过等),当使用文字时,你可以在编译时验证它。offset
unwrap()
这个想法是创建一个表示“范围内值”的结构:
mod private {
pub trait Sealed {}
impl Sealed for u8 {}
impl Sealed for u16 {}
}
use std::ops::RangeInclusive;
use std::marker::PhantomData;
pub trait RangeLimited: private::Sealed {
const ALLOWED_RANGE: RangeInclusive<u8>;
}
impl RangeLimited for u8 {
const ALLOWED_RANGE: RangeInclusive<u8> = 0..=7;
}
impl RangeLimited for u16 {
const ALLOWED_RANGE: RangeInclusive<u8> = 0..=15;
}
#[derive(Debug, Clone, Copy)]
pub struct InRange<T> {
value: u8,
_marker: PhantomData<T>,
}
impl<T: RangeLimited> InRange<T> {
pub fn new(value: u8) -> Option<Self> {
T::ALLOWED_RANGE.contains(&value).then_some(Self {
value,
_marker: PhantomData,
})
}
pub fn get(self) -> u8 {
self.value
}
}
const fn do_something_with_a_bit_offset<T>(offset: InRange<T>) -> T {
// .. some ops return a T ..
}
用法如下:
let mask = do_something_with_a_bit_offset::<u8>(InRange::new(0).unwrap());
当使用文字(或任何常量值)时,编译器可以在编译时对其进行验证,并为您节省一个 .为此,我们需要一个宏:unwrap()
impl<T: RangeLimited> InRange<T> {
#[doc(hidden)]
pub const fn new_unchecked(value: u8) -> Self {
Self {
value,
_marker: PhantomData,
}
}
}
#[macro_export]
macro_rules! in_range {
($type:ty, $v:expr) => {{
const __IN_RANGE_VALUE: $crate::InRange<$type> = {
const __IN_RANGE_VALUE: $type = $v;
if !(*<$type as $crate::RangeLimited>::ALLOWED_RANGE.start() <= __IN_RANGE_VALUE
&& __IN_RANGE_VALUE <= *<$type as $crate::RangeLimited>::ALLOWED_RANGE.start())
{
panic!("value out of range");
}
$crate::InRange::new_unchecked(__IN_RANGE_VALUE)
};
__IN_RANGE_VALUE
}};
}
用法如下:
let mask = do_something_with_a_bit_offset(in_range!(u8, 0));
这是强制编译器在编译时评估它,否则它将在运行时评估它,这将是运行时崩溃。const
如果你想依靠值在范围内来获得健全性,你需要使 unsafe 并在宏中围绕它添加一个块(但是,要小心不要也包装在同一个不安全的块中)。new_unchecked()
unsafe
$v
评论