Скажем, у меня есть функция, которая принимает аргумент типа u16
. Есть ли элегантный способ определения пользовательского типа данных, который ведет себя точно так же, как u16
, но имеет только значения от 0 до 100?
Тип данных, который принимает только диапазон значений
Ответ 1
Насколько я понимаю, для этого требуются зависимые типы, которых у Rust нет. Это не требует зависимых типов (см. комментарии), но Rust по-прежнему не имеет необходимой поддержки.
В качестве обходного пути вы можете создать новый тип, который вы проверите сами:
#[derive(Debug)]
struct Age(u16);
impl Age {
fn new(age: u16) -> Option<Age> {
if age <= 100 {
Some(Age(age))
} else {
None
}
}
}
fn main() {
let age1 = Age::new(30);
let age2 = Age::new(500);
println!("{:?}, {:?}", age1, age2);
println!("{}, {}", std::mem::size_of::<Age>(), std::mem::size_of::<u16>());
}
Конечно, он не ведет себя точно так же, как u16
, но вы тоже этого не хотите! Например, u16
может выходить за рамки 100... Вы должны будете рассуждать, если имеет смысл добавлять/вычитать/умножать/делить и т.д. Также ваш новый тип.
Важно отметить, что этот новый тип занимает столько же места, сколько u16
- тип оболочки эффективно стирается при компиляции кода. Средство проверки типов проверяет, что все находится в зацеплении до этого момента.
Ответ 2
К сожалению, внутри стандартного ящика такого нет.
Тем не менее, вы можете сделать это самостоятельно оптимизированным способом с ночными универсальными константами. Пример:
#![feature(const_generics)]
pub struct BoundedI32<const LOW: i32, const HIGH: i32>(i32);
impl<const LOW: i32, const HIGH: i32> BoundedI32<{LOW}, {HIGH}> {
pub const LOW: i32 = LOW;
pub const HIGH: i32 = HIGH;
pub fn new(n: i32) -> Self {
BoundedI32(n.min(Self::HIGH).max(Self::LOW))
}
pub fn fallible_new(n: i32) -> Result<Self, &'static str> {
match n {
n if n < Self::LOW => Err("Value too low"),
n if n > Self::HIGH => Err("Value too high"),
n => Ok(BoundedI32(n)),
}
}
pub fn set(&mut self, n: i32) {
*self = BoundedI32(n.min(Self::HIGH).max(Self::LOW))
}
}
impl<const LOW: i32, const HIGH: i32> std::ops::Deref for BoundedI32<{LOW}, {HIGH}> {
type Target = i32;
fn deref(&self) -> &Self::Target {
&self.0
}
}
fn main() {
let dice = BoundedI32::<1, 6>::fallible_new(0);
assert!(dice.is_err());
let mut dice = BoundedI32::<1, 6>::new(0);
assert_eq!(*dice, 1);
dice.set(123);
assert_eq!(*dice, 6);
}
А потом вы можете выполнять математику и т.д.
Если вы хотите выбрать границы во время выполнения, вам не нужна эта функция, и вам просто нужно сделать что-то подобное:
pub struct BoundedI32 {
n: i32,
low: i32,
high: i32,
}
Вы также можете использовать ящик типа bounded-integer
, который позволяет генерировать ограниченное целое число на лету с помощью макроса.