type OneOrThree = 1 | 3
type Nullable<T> = T | null
Its not just Ada - Rust also has non-zero integer types in the standard library. Eg:
This was written mostly let Option<NonZeroU32> take up the same amount of space as u32. Ie, if you know your integer will never be zero, you can reuse the zero slot as a sentinal None value in a way thats API-compatible with everything else in rust.
I would suggest avoiding this kind of thinking. Set theory and type theory are separate. Mixing them will only cause confusion and suffering.
> without changing the API
Pretty sure that loosening the return type constraints of a function or tightening the constrains of a variable that a function accepts would both change the API, is my understanding incorrect?
> This was written mostly let Option<NonZeroU32> take up the same amount of space as u32
Why not just use u32 then? These are basically the same thing after all.
There are some languages - notably, Ada - where this (types as sets) is very much explicit in the language.
> Why not just use u32 then?
For u32, 0 is "just another value" - there's nothing special about it. If you pass it to some other code, there's nothing telling them that they must check for zero, and must not treat zero as actual zero value (and if they forget, it will silently give the wrong answer).
Option<NonZeroU32> requires them to pattern-match on None/Some, and if they get Some, it's guaranteed to be non-zero. That None is stored as zero is an implementation detail in that case, and doesn't leak onto the abstraction level on which code operates.