Why I cannot compare two `&'static str`s in a const context?

macro_rules! id_name_conv {
    ($($id:literal -> $name:literal)*) => {
        const fn id2name(id: u32) -> &'static str {
            match id {
                $($id => $name,)*
                _ => unreachable!(),
            }
        }

        const fn name2id(name: &'static str) -> u32 {
            match name {
                $($name => $id,)*
                _ => unreachable!(),
            }
        }
    };
}

id_name_conv!(
    0 -> "a"
    1 -> "b"
);

raises:

error[E0015]: calls in constant functions are limited to constant functions, tuple structs and tuple variants
  --> main.rs:13:23
   |
13 |                       $($name => $id,)*
   |                         ^^^^^
...
20 | /     id_name_conv!(
21 | |         0 -> "a"
22 | |         1 -> "b"
23 | |     );
   | |_____- in this macro invocation
   |
   = note: this error originates in the macro `id_name_conv` (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to previous error

For more information about this error, try `rustc --explain E0015`.

After search I found an open issue, proves that the error is caused by comparing two &'static strs, and also mentions that comparing two &'static [u8]s is fine. Without getting into the confusing error reporting, is there any reason why this cannot be implemented?

This isn't actually quite correct, as this is still a compile error:

const fn f(lhs: &'static [u8], rhs: &'static [u8]) -> bool { lhs == rhs }
error[E0277]: can't compare `[u8]` with `_` in const contexts
 --> src/lib.rs:1:66
  |
1 | const fn f(lhs: &'static [u8], rhs: &'static [u8]) -> bool { lhs == rhs }
  |                                                                  ^^ no implementation for `[u8] == _`
  |
  = help: the trait `~const PartialEq<_>` is not implemented for `[u8]`
note: the trait `PartialEq<_>` is implemented for `[u8]`, but that implementation is not `const`
 --> src/lib.rs:1:66
  |
1 | const fn f(lhs: &'static [u8], rhs: &'static [u8]) -> bool { lhs == rhs }
  |                                                                  ^^
  = note: required for `&[u8]` to implement `~const PartialEq<&_>`

(The error is clearer now than it used to be, although it's not ideal that it's referring to unstable ~const syntax rather than just saying the trait implementation cannot be used in const contexts.)

It is interesting that it works in a match, though:

const X: &[u8] = b"";
const fn f(s: &'static [u8]) -> bool {
    match s {
        X => true,
        _ => false,
    }
}

AIUI the reason this occurs is because matching on &str goes through the derive(PartialEq) implementation, whereas matching on &[_] actually becomes a slice pattern instead, and the primitive matching on both slices and u8 can be used on const contexts as they don't go through PartialEq/StructuralEq.

You can see this in effect on the playground by choosing to show MIR:

pub fn str(s: &'static str) -> bool {
    match s {
        "" => true,
        _ => false,
    }
}

pub fn u8s(s: &'static [u8]) -> bool {
    match s {
        b"" => true,
        _ => false,
    }
}
// WARNING: This output format is intended for human consumers only
// and is subject to change without notice. Knock yourself out.
// Editorialized by @CAD97
fn str(_s: &str) -> bool {
    let _return: bool;
    let _eq: bool; // tmp

    bb0: {
        _eq = <str as PartialEq>::eq(_s, const "") -> bb1;
    }

    bb1: {
        switchInt(move _eq) -> [false: bb2, otherwise: bb3];
    }

    bb2: {
        _return = const false;
        goto -> bb4;
    }

    bb3: {
        _return = const true;
        goto -> bb4;
    }

    bb4: {
        return;
    }
}

fn u8s(_s: &[u8]) -> bool {
    let _return: bool;
    let _len_s: usize;
    let _len_pat: usize;
    let _4: bool;

    bb0: {
        _len_s = Len((*_s));
        _len_pat = const 0_usize;
        _eq = Eq(move _len_s, move _len_pat);
        switchInt(move _4) -> [false: bb1, otherwise: bb2];
    }

    bb1: {
        _return = const false;
        goto -> bb3;
    }

    bb2: {
        _return = const true;
        goto -> bb3;
    }

    bb3: {
        return;
    }
}
1 Like

To prevent duplication, I have already filed an issue about this.

1 Like

In fact, you can compare &str in a const context now. Const evaluation supports while-loop, comparing bytes and slice indexing.

https://docs.rs/const-str/latest/const_str/macro.equal.html

Good job! But how to use it in match? We cannot impl PartialEq for primitive types.

Seems that I have to generate ifs. What the progress of impl it in std?

Maybe this: https://github.com/rust-lang/rust/issues/67792

My final code is like:

    const fn const_bytes_equal(lhs: &[u8], rhs: &[u8]) -> bool {
        if lhs.len() != rhs.len() {
            return false;
        }
        let mut i = 0;
        while i < lhs.len() {
            if lhs[i] != rhs[i] {
                return false;
            }
            i += 1;
        }
        true
    }
    
    const fn const_str_equal(lhs: &str, rhs: &str) -> bool {
        const_bytes_equal(lhs.as_bytes(), rhs.as_bytes())
    }

    macro_rules! id_name_conv {
        ($($id:literal -> $name:literal)*) => {
            const fn id2name(id: u32) -> &'static str {
                match id {
                    $($id => $name,)*
                    _ => unreachable!(),
                }
            }
    
            const fn name2id(name: &'static str) -> u32 {
                $(if const_str_equal(name, $name) {
                    return $id
                })*
                unreachable!();
            }
        };
    }
    
    id_name_conv!(
        0 -> "a"
        1 -> "b"
    );
    
    assert_eq!(id2name(0), "a");
    assert_eq!(name2id("b"), 1);
1 Like