Literal integer arguments in fn?

Say I want a function to accept only the following numbers: 1, 10, 20

fn myfn(arg: 1|10|20){}

How can this be enforced in Rust?

I'd use an enum for that.

enum MyNumber {
    One = 1,
    Ten = 10,
    Twenty = 20,
}

fn myfn(n: MyNumber) {
    // Convert `n` to a number with `n as i32` for example
}
5 Likes

I'd go with @jofas suggestion, but below is probably the closest you could get to your snippet:

pub trait Acceptable {}
pub enum Number<const N: usize> {}

impl Acceptable for Number<1> {}
impl Acceptable for Number<10> {}
impl Acceptable for Number<20> {}

fn accept<const N: usize>() where Number<N>: Acceptable {
    println!("Accepted! {N}");
}

#[test]
fn test_accept() {
    accept::<1>();
    accept::<10>();
    accept::<20>();
    accept::<30>(); // won't compile
}
1 Like

There's an interest in "pattern types", but if it ever comes to be, it will be some time from now.

@cardoso made a great suggestion if you know your argument at compile time. If not, I'd use a guard clause.

fn myfn(arg: i32) {
    if !matches!(arg, 1 | 2 | 10) {
        return;
    }

    todo!("rest of `myfn`")
}
1 Like

well that would be awesome. I'm new to Rust and there is some patterns I really dislike. Even if the provided solutions here work, they look verbose and totally unnecessary. I will simply document my function accordingly instead of doing those. The aim was to give users some IDE info on the specific argument before compilation. We can see some beauty from other languages which comes together in Rust, but at the same time there are bits that look awful. I hope this improves in the future.

Out of curiosity, what other languages have you programmed in that solve this problem in a more elegant way? I can only think of C++ template shenanigangs (not very nice IMO) or dependently typed languages like Idris (that nobody use).

I just got the idea from Typescript in this case:

function myfn(arg:1|2|3){}
type X = "1"|"2"|"3"
1 Like

Nice, I forgot that you could do that. Thanks for the reminder!

I definitely agree with you on providing documentation. Do you have an example from another language that might satisfy you?

A language with that exact syntax would be typescript, but that offers zero runtime guarantees.

let x = 5;

function myfn(arg: 1 | 10 | 20) { }

myfn(x) // gives error: Argument of type 'number' is not assignable to parameter of type '1 | 10 | 20'. 

This requires a runtime check anyways, and is completely incompatible with exhaustive pattern matching.

I do like OCaml's approach

let myfn = function
  | 1 | 10 | 20 -> true
  | _ -> false

Rust could have something similar, but I'm really not sure what I would want the syntax to be. Maybe this?

fn myfn(arg: i32) match arg {
   1|10|20 => todo!(),
   _ => panic!(),
}

I like this personally, but you could very easily just move the match inside the function block.

If it's just about the IDE providing information about what values the function expects, and it has zero runtime effects, maybe this?

fn myfn(arg: i32 @ 1|10|20) {
   todo!()
}
fn myfn(arg: i32 @ 1|10|20) {
   todo!()
}

This looks like a good compromise!

As I posted above, Typescript does have this and provides the users IDE with info on the function arguments. The runtime effects would be that the API Server would return an error.

Your examples would already be an elegant solution to this.

Should perhaps be noted that TypeScript is pretty unique among commonly-used programming languages to have this (literal types and union types). No other popular language I know of can do that. So it's not exceedingly strange that Rust cannot either. One reason TS can do that is that it can afford to play a bit fast and loose with its types given that it's a gradually typed language that simply becomes JS if you remove all type annotations.

It'll be very cool if we ever get pattern types or something similar, but there are big issues that have to solved first, related to subtyping, variance, and soundness.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.