I don't think I understand what you are looking for..
Why not just use assert!
then? There is also bool::then to turn a bool
into an Option
.
What I am missing currently is a type that automatically checks its values for some constraint, or a predicate function, so that I can have guarantees for the data of that type, which are enforced for me. Currently, we have to manually enforce invariants for the data of a type, by carefully constructing it and managing write access. This is just like using a u32
to store boolean values. So basically what I am looking for is an extension of the type system, which allows me to constrain types through predicate functions, and verifying these properties by the compiler. However as @Tom-Phinney mentioned, this is probably more complex than I can currently comprehend.
In your example you are showing how Decoded
is constructed. Depending on what is stored in Decoded
, you could use the current type system to guarantee correct values, or not. For example, if you were decoding just arbitary numbers, you could use u64
and store numbers as large as you could fit. But, there is no type currently, that can only be exactly 40
to 50
, or a type that can only be an even integer, or solutions of 0 = 2x - y
. Or a string starting with aaa
.
The difficult part is validating these invariants at compile time. How can you know if a value of the range (40, 50)
is 49
right now, and you can increment it, or if it is 50
already and incrementing would break the invariant? You have to check this at runtime, each time you increment the value.
But I would use a type like this too, one that can return an error whenever I change its value. This probably already exists though, for example, there is the Range type, even with syntax sugar: 40..=50
.
I think my fundamental problem is that types and values have a very thin and washed out border between them. In a way, they are the same, because they are both stored as data. You could store a constant value of type Range
:
const RANGE_40_TO_50: Range = Range::new(40, 51);
or you could build a type
struct Range40To50(u32);
impl Range40To50 {
const START: u32 = 40;
const END: u32 = 51;
}
It seems like types are actually just constant values with a lot of syntax sugar. Instead of defining the byte offset for each field (a constant), the compiler infers it for you when you write the struct definition. I would really like to extend on that idea and make meta programming more expressive. But maybe that is a project for a new language altogether, I don't know.