Тип данных, который принимает только диапазон значений

Допустим, у меня есть функция, которая принимает аргумент типа 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()
);
}
Другие вопросы по тегам