[Idea] allow unresolved symbols in statements of match arms that don't match (when matching on const)

Topic : Doing match on a const variable

Suggestion : Let rustc allow unresolved symbols inside match arms that don't match the const variable.

Example-1 : All statements of every match arm don't contain any unresolved symbols

  • The program below compiles successfully.
  • Every match arm doesn't contain any unresolved symbols.
  • Compiled in Debug mode: binary for every match arm is generated.
  • Compiled in Release mode: binary for only the matching arm is generated.
// Godbolt link: https://godbolt.org/z/mnAuV_
enum Menu {
    SANDWICH,
    SPAGHETTI,
    SALAD,
    UNDEFINED,
}
const what : Menu = Menu::SANDWICH;

pub fn mymenu() {
    match what {
        Menu::SANDWICH => println!("SANDWICH!"),
        Menu::SPAGHETTI => println!("SPAGHETTI!"),
        Menu::SALAD => println!("SALAD!"),
        Menu::UNDEFINED => println!("NO LUNCH!")
    }
}

Example-2 : Statements of match arms may contain unresolved symbols (due to conditional compilation)

  • There is a match statement on a const variable in the main function.
  • Some functions(make_sandwich, make_spaghetti, make_salad, no_lunch) are conditionally compiled in order to optimize program binary size.
  • The program below does not compile, since invocations to conditionally compiled functions become unresolved symbols inside match arms that don't match the const variable. (ex. error[E0425]: cannot find function 'make_sandwich' in this scope)
pub enum Menu {
    SANDWICH,
    SPAGHETTI,
    SALAD,
    UNDEFINED,
}
cfg_if::cfg_if! {
    if #[cfg(all(
        feature="SANDWICH", 
        not(feature="SPAGHETTI"), 
        not(feature="SALAD")
    ))] {
        const mylunch : Menu = Menu::SANDWICH;
        pub fn make_sandwich() {
            println!("MAKE SANDWICH!");
        }
    }
    else if #[cfg(all(
        not(feature="SANDWICH"), 
        feature="SPAGHETTI", 
        not(feature="SALAD")
    ))] {
        const mylunch : Menu = Menu::SPAGHETTI;
        pub fn make_spaghetti() {
            println!("MAKE SPAGHETTI!");
        }
    }
    else if #[cfg(all(
        not(feature="SANDWICH"), 
        not(feature="SPAGHETTI"), 
        feature="SALAD")
    )] {
        const mylunch : Menu = Menu::SALAD;
        pub fn make_salad() {
            println!("MAKE SALAD!");
        }
    }
    else {
        const mylunch : Menu = Menu::UNDEFINED;
        pub fn no_lunch() {
            println!("NO LUNCH FOR TODAY!");
        }
    }
}
fn main() {
    // the if-statement compiles only when: feature="SANDWICH"
    if let Menu::SANDWICH = mylunch {
        make_sandwich();
    }
    // the following match statement does not compile
    match mylunch {
        Menu::SANDWICH => make_sandwich(),
        Menu::SPAGHETTI => make_spaghetti(),
        Menu::SALAD => make_salad(),
        Menu::UNDEFINED => no_lunch(),
    }
}

Regarding the suggestion..

  • Expected benefit

    Allow programmers to write conditionally compiled programs more elegantly (without having to write #cfg(~) all over the place)
  • Doubts

    Is conditional compilation on functions really helpful in optimizing program binary size? (: Is writing code like Example-2 even necessary to optimize program binary size?)

It's a very rough idea, and I don't yet have an idea on the implementation side.. But I would really appreciate any feedback on this direction! :slight_smile:

If code is unreachable, it (is allowed to be) gets optimized out, so with the hypothetical perfect optimizer, it only costs compile time, and not binary size.

If cfg attributes aren't allowed on match arms, they probably should be (caveat: how do you tell an attribute on the arm apart from an attribute on the pattern?). That would allow something like the following:

match mylunch {
    #[cfg(feature = "SANDWICH")]
    Menu::Sandwich => make_sandwich(),
    // ...
    _ => unreachable!(),
}

(As an offtopic tangent, it seems mean that if I want both a sandwich and a salad, I get nothing. Features are supposed to be purely additive (to the public API), and sticking to this throughout the codebase, public or private, makes working with them easier.)

1 Like

Something something if constexpr...

1 Like

You are right!.. what happened was that I had set all the functions to pub in my personal project, and the generated binary included all the compiled binary for functions that weren't called inside my project.. So I was looking into conditional compilation as a solution, and just now I realized that setting the functions to pub(crate) would let the optimizer optimize away all the compiled binary for the functions that are not actually called in the program..

This RFC seems relevant (merged).

Oh, I wasn't aware that features are supposed to be purely additive. Thanks for letting me know!

Thanks for your feedback!

That seems very similar to what I was thinking! Thank you for letting me know :grinning:

They are:

fn foo() {
    match 0 { //~ ERROR non-exhaustive patterns: type `i32` is non-empty
        #[cfg(FALSE)]
        _ => {}
    }
}
1 Like