Search code examples
rustcompile-time

Is it possible in Rust to determine whether a function argument is a compile-time constant?


In Rust, it's possible to assert that the result of an expression is a compile-time constant by using it to initialise a const. However, this sort of check doesn't work across a function boundary. For example, the following code errors at compile time:

struct StringLiteral(&'static str);
impl StringLiteral {
    const fn new(s: &'static str) -> Self {
        const _: &'static str = s;
        StringLiteral(s)
    }
}

with "attempt to use a non-constant value in a constant", even if StringLiteral::new is never called.

It seems like it should be possible to make this sort of assertion by checking to see whether the new function is actually running at compile-time or not – const fns can be called either at runtime or compile-time, and somehow asserting that the function is being run at compile-time would be one possible solution to the problem. But I haven't been able to find a function/method/intrinsic in the standard library that would determine that.

Is it possible to write a const fn such that it runs successfully only when its argument is a compile-time constant (either via detecting whether it's being run at compile-time, or via some other mechanism)?

One approach that does not work: simply requiring a 'static lifetime is insufficient because it's possible to create values with such lifetimes during runtime, e.g. via the use of String::into_boxed_str followed by Box::leak.

(Context: I'm attempting to produce a data type that can contain only literal strings, not strings that were calculated at runtime, as a sort of defence-in-depth mechanism that statically guarantees that the strings can't be controlled by an attacker: the requirement that a string must have been literally present in the program's source code makes it impossible for an attacker to put their own custom strings there.)


Solution

  • From Sven's comment, you can create a trait with an associated constant.

    pub trait CompileTime {
        const C: &'static str;
    }
    
    pub struct HelloStr;
    
    impl CompileTime for HelloStr {
        const C: &'static str = "Hello";
    }
    

    This is a decent amount of boilerplate for a simple thing, so you may want a macro, such as:

    macro_rules! comptime {
        ($name:ident, $string:literal) => {
            pub struct $name;
    
            impl CompileTime for $name {
                const C: &'static str = $string;
            }
        }
    }
    
    comptime!(HelloStr, "Hello");
    

    Future rust

    When adt_const_params becomes stable, you can use a const generic &str, which works better with your original code:

    const fn new<const S: &'static str>()