Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to create a data type that only accepts a range of values?

Tags:

types

rust

I have a function that takes an argument of type u16. Is there an elegant way to define a custom data type that behaves exactly like a u16 but only has values between 0 and 100?

like image 748
Kurtis Nusbaum Avatar asked Dec 28 '14 04:12

Kurtis Nusbaum


People also ask

What is range data type?

Range means the maximum and minimum value that can be stored inside the variable of a given type. For example if you have unsigned char and if we assume that the size of the datatype is 8 bits then you can store values ranging from 0 - 2^8-1 i.e. 0-255 inside it.

What is the range of float data type?

16 bits (2 bytes), ushort, 0 to 65535.

What is the range of values that can be stored by data type in C?

Thus, the defined range here is -128 to +127. But the unsigned character is only capable of holding the positive values. Thus, the range for such characters is from 0 to 255. These character data types are capable of storing the ASCII characters or the numbers that are equivalent to the ASCII characters.


1 Answers

As I understand it, that requires dependent types, which Rust does not have. This doesn't require dependent types (see comments) but Rust still doesn't have the support needed.

As a workaround, you could create a newtype that you verify yourself:

#[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>()
    );
}

Of course, it doesn't behave exactly like a u16, but you don't want it to, either! For example, a u16 can go beyond 100... You'd have to reason out if it makes sense to add/subtract/multiply/divide etc your new type as well.

For maximum safeguarding, you should move your type and any associated functions into a module. This leverages Rust's visibility rules to prevent people from accidentally accessing the value inside the newtype and invalidating the constraints.

You may also want to implement TryFrom (from u16 to your type) or From (from your type to u16) to better integrate with generic code.

An important thing to note is that this newtype takes the same amount of space as a u16 - the wrapper type is effectively erased when the code is compiled. The type checker makes sure everything meshes before that point.

like image 165
Shepmaster Avatar answered Oct 25 '22 02:10

Shepmaster