Тип данных, который принимает только диапазон значений
Допустим, у меня есть функция, которая принимает аргумент типа u16
, Есть ли элегантный способ определить пользовательский тип данных, который ведет себя точно так же, как u16
но имеет только значения от 0 до 100?
4 ответа
Насколько я понимаю, для этого нужны зависимые типы, которых у 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
- тип оболочки эффективно стирается при компиляции кода. Средство проверки типов проверяет, что все находится перед этим моментом.
К сожалению, в ящике std такого нет.
Однако вы можете сделать это самостоятельно с помощью общих ночных констант. Пример:
#![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
это позволяет генерировать ограниченное целое число на лету с помощью макроса.
С ночной функцией
generic_const_exprs
, это можно проверить во время компиляции:
#![feature(generic_const_exprs)]
struct If<const COND: bool>;
trait True {}
impl True for If<true> {}
const fn in_bounds(n: usize, low: usize, high: usize) -> bool {
n > low && n < high
}
struct BoundedInteger<const LOW: usize, const HIGH: usize>(usize);
impl<const LOW: usize, const HIGH: usize> BoundedInteger<LOW, HIGH>
where
If<{ LOW < HIGH }>: True,
{
fn new<const N: usize>() -> Self
where
If<{ in_bounds(N, LOW, HIGH) }>: True,
{
Self(N)
}
}
Сообщения об ошибках не самые лучшие, но это работает!
fn main() {
let a = BoundedInteger::<1, 10>::new::<5>();
let b = BoundedInteger::<10, 1>::new::<5>(); // ERROR: doesn't satisfy `If<{ LOW < HIGH }>: True`
let c = BoundedInteger::<2, 5>::new::<6>(); // ERROR: expected `false`, found `true`
}
Насколько мне известно, не совсем так. Но вы можете использовать черту, чтобы приблизиться. Пример, где тоннаж - это 8-битное целое число без знака, которое должно быть 20-100 и кратно 5:
pub trait Validator{
fn isvalid(&self) -> bool;
}
pub struct TotalRobotTonnage{
pub tonnage: u8,
}
impl Validator for TotalRobotTonnage{
//is in range 20-100 and a multiple of 5
fn isvalid(&self) -> bool{
if self.tonnage < 20 || self.tonnage > 100 || self.tonnage % 5 != 0{
false
}else{
true
}
}
}
fn main() {
let validtonnage = TotalRobotTonnage{tonnage: 100};
let invalidtonnage_outofrange = TotalRobotTonnage{tonnage: 10};
let invalidtonnage_notmultipleof5 = TotalRobotTonnage{tonnage: 21};
println!("value {} [{}] value {} [{}] value {} [{}]",
validtonnage.tonnage,
validtonnage.isvalid(),
invalidtonnage_outofrange.tonnage,
invalidtonnage_outofrange.isvalid(),
invalidtonnage_notmultipleof5.tonnage,
invalidtonnage_notmultipleof5.isvalid()
);
}