Constants which build on each other?

Here's a pattern I use in other languages, e.g. Ruby:

PORT = "3000"
URL  = "http://localhost:#{PORT}/"

In Rust, this doesn't seem possible, because format! isn't allowed on the right hand side:

const PORT: &str = "3000"
const URL: &str  = // ???

I haven't found a workaround because if (say) I make URL an immutable variable with let, then it can't be at the "top level" next to the const any more; let is only legal in a function.


EDIT: I just learned about concat!. Maybe it'd work?
EDIT 2: Nope, I get an error that concat! only works with literals, not a const.

I don't think there's an exact match for this concept in Ruby. Or, more accurately, we don't have one which uses const expressions.

In rust, const means fully compile time evaluated, and we don't yet have support for doing complex things at compile time. In the future, something like format!() could become usable here. But until then, the computation needs to be performed at runtime.

This isn't necessarily a bad thing, though - the variables in ruby would have been evaluated at something approximating runtime anyways, and you can still get pretty good performance, as well as treating things as constants.

To that end, I suggest you use lazy_static with format!(). It'll be a bit more boilerplate than pure const variables, but it allows for initializing each static on first use, and the usage looks exactly like using a regular static or const variable. Note that it isn't a const - usages won't be replaced directly with the value. But I assume that in this case, that's fine.

This would look something like:

use lazy_static::lazy_static;

static PORT: &str = "3000";
lazy_static! {
    static ref URL: String = format!("http://localhost:{}", PORT);
    static ref HTTPS_URL: String = format!("https://localhost:{}", PORT);
}

I've included two variables using PORT to highlight lazy_static's nature of allowing multiple definitions in the same macro invocation. You could also include PORT in the lazy_static! block if it makes things clearer:

use lazy_static::lazy_static;

lazy_static! {
    static ref PORT: &'static str = "3000";
    static ref URL: String = format!("http://localhost:{}", *PORT);
    static ref HTTPS_URL: String = format!("https://localhost:{}", *PORT);
}

There's a slight annoyance in that you need to explicitly dereference these statics with * if you want to use them in a generic context, but I'm not sure there's a better way to do this. The * is explicitly saying "I don't want the lazy_static initializing wrapper, I want its contents", and can be elided if its type is obvious. (we just need it here because format!() accepts any type of thing to print).

If you want slightly clearer, but also potentially much more verbose code, maybe once_cell with a url() function providing access? once_cell implements most of the functionality of lazy_static, but without the macro magic. In some situations it can be clearer, but in others it isn't.

2 Likes

Thanks! Yep, like you intuited, I'm most interested in simply keeping the config info at the top in a constant-like datatype. (Ruby doesn't even have true constants; one simply gets a runtime warning.) Haskell, on the other hand, is pretty nice, since everything is immutable and a function, "constants" at the top of a code section is easy.

1 Like

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.