仅采用一系列值的数据类型

时间:2014-12-28 04:20:29

标签: rust

假设我有一个带有u16类型参数的函数。是否有一种优雅的方式来定义一个行为与u16完全相同但只有0到100之间的值的自定义数据类型?

3 个答案:

答案 0 :(得分:8)

据我所知,这需要dependent types,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>());
}

当然,它不像<{1}}那样完全,但你也不想要它!例如,u16可以超过100 ...你必须推断是否有必要对你的新类型进行加/减/乘/除等。

需要注意的一点是,这个newtype占用的空间与u16相同 - 在编译代码时,包装类型会被有效擦除。类型检查器确保在该点之前所有内容都是网格。

答案 1 :(得分:3)

不幸的是,std板条箱中没有这样的东西。

但是,您可以使用夜间通用const以优化的方式自己进行操作。示例:

#![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这样的板条箱,该板条箱可以通过宏即时生成有界整数。

答案 2 :(得分:0)

据我所知,不完全是。但是您可以使用特征来接近。例如,其中吨位是一个无符号的 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()
);
}