- Feature name:
limited-value-types
- Start Date: 2025-04-07
- RFC PR: none
- RFC Issue: none
Summary
Types in Rust usually have a set of possible values, sometimes theoretically infinite but mostly not. This RFC proposes to make it possible to have a limit on how many values a type has, with compile-time constant parameters.
Motivation
The motivation for limited value types is cases where you wish to have certain values for a type and do not want to have invalid values. Eg. an error code.
// 200: Success
// 201: Create called
// 400: Bad Request
// 401: ...
// and so on
async fn my_request(data: Data, url: &str) {
let status_code: bound(i32, STATUS_CODES) = get_status_code(data, url);
}
This could be solved with an enum:
pub enum Status {
Success(u16),
Err(u16)
}
But this will cause an issue if a number like 1032 is sent by mistake and also requires a lot more boilerplate for classification. Hence, a bounded number is better for easy convertibility.
Guide-level explanation
Think about a bound
type as you would a normal type, except you can only use specific values. So, if you wish to use a status code and you don't want to use an enum, you can use a bound
type. It's also more efficient to use.
An example of how you can use this code:
Example
// If the function returns an improper value, the bound type will be incorrect.
// The bound has to exactly match the return type of the function.
let x: bound(i32, [7, 4, i32::MAX]) = receive_whatever(data);
let mut y: bound(i8, [0, 4, i8::MAX]) = 0;
y = 4;
// Whoops! I assigned an improper value to y!
// y = 8;
// error: value '8' cannot be assigned to variable of type bound(i8, [0, 4, i8::MAX])
// if the return value 3 we would get a type mismatch error
fn wow(something: i32) -> bound(i8, [0, 4, i8::MAX]) {
4
}
// a will be an i8, not a bounded i8
let a = wow(3);
let a: bound(i8, [0, 4, i8::MAX]) = wow(3);
// bound constraints are enforced at compile time, not runtime
It's also possible to match on a bound, eg.
let status: StatusCodes = Request::get_status_code();
match status {
200 => println!("Success!");
// etc
}
Reference-level explanation
- A
bound
type is a type with specific values for any primitive, struct, enum or slice value. - Said
bound
type will have a compile-time error if the value that is assigned to it is different from the values that have been assigned to the bound - All
bound
types implement traits directly from their original types bound
types can have a value be a variable, function return type or function parameters only- Return types of functions that are
bound
can be assigned to the original type, the exact samebound
type, or a supertype of thebound
type. - If an operation is conducted on two
bound
types, the result is the original type bound
types have to be annotated, they cannot use type inference- A bounded type does not require a constructor
- An error will be thrown if a value that is not in the original type's purview is used in the
bound
type annotation. - A function that returns the original type may not be assigned to a
bound
type. const
values of a type orconst
arrays of a type may be used to create abound
type.- Bounds can have generics with trait objects.
- All types of a
bound
can be matched. - Other pattern-matching can also be implemented, including
if let
andlet else
blocks.
Drawbacks
We should not do this because:
- The type rules are too strict
- Bounds may require a lot of repetition
- Enums are sometimes more intuitive (mostly bounds are more intuitive)
Rationale and alternatives
Enums are possible, however require a lot of boilerplate to function properly. This provides a way for similar numerical structure to C-type enum numerals assigned, however bounded.
This could be also implemented via a cenum
, but I think this is enough to provide proper functionality.
Future possibilities
cenum
, like this RFC discussed, is a possible way to add C-style enums to Rust to convert between other types and enums easily.