【问题标题】:Is there a way to create a data type that only accepts a range of values?有没有办法创建只接受一系列值的数据类型?
【发布时间】:2020-01-24 00:43:25
【问题描述】:

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

【问题讨论】:

    标签: types rust


    【解决方案1】:

    据我了解,这需要 dependent types,而 Rust 没有。 这不需要依赖类型(请参阅 cmets),但 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);
        assert_eq!(
            std::mem::size_of::<Age>(),
            std::mem::size_of::<u16>()
        );
    }
    

    当然,它的行为完全不像u16,但你也不希望它这样做!例如,u16 可以超过 100... 如果对您的新类型进行加/减/乘/除等有意义的话,您必须推理。

    为了最大限度地保护,您应该将您的类型和任何相关功能移到一个模块中。这利用了 Rust 的可见性规则来防止人们意外访问 newtype 中的值并使约束无效。

    您可能还希望实现TryFrom(从u16 到您的类型)或From(从您的类型到u16)以更好地与通用代码集成。

    需要注意的重要一点是,这种新类型与u16 占用的空间量相同——在编译代码时,包装类型会被有效擦除。类型检查器确保在该点之前一切都网格化。

    【讨论】:

    • 这或多或少是我猜测我必须做的。我认为对于我的行为,我实际上只想夹紧。因此,如果它们提供其他值也没关系,但任何高于 100 的值都会被限制为 100。
    • 它不需要依赖类型,它们用于将符号(表示运行时值)链接到类型参数值,并且稍微复杂一些。 Ada 没有依赖类型,但允许您定义此类自定义积分(语言内置),而 C++ 具有非类型模板参数以实现类似的效果。
    • @MatthieuM。谢谢!这个概念有通用名称吗?当我之前遇到它时,我被告知它是“依赖类型”。我想知道真名(如果有的话!)。
    • @Shepmaster:如果有的话,我不知道。在 C++ 中,它们被称为非类型模板参数,这实际上是特定于语言的......
    • @VagelisProkopiou 使用 Rust 的模块系统解决了这个问题。将Age 放在模块内,不要公开该字段。
    【解决方案2】:

    不幸的是,std crate 中没有这样的东西。

    但是,您可以使用夜间通用 const 以优化的方式自己完成,计划在 Rust 1.51 中稳定。示例:

    // 1.51.0-nightly (2020-12-30)
    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 这样的 crate,它允许使用宏即时生成有界整数。

    【讨论】:

      【解决方案3】:

      使用夜间功能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`
      }
      

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          猜你喜欢
          • 2021-07-24
          • 1970-01-01
          • 2016-07-16
          • 2015-07-30
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多