0

I would like to initialise some constants at the very top of my main.rs file, like so:

const PRIVATE_KEY: Vec<u8> = std::fs::read("./jwtRS256.key").unwrap();
const PUBLIC_KEY: Vec<u8> = std::fs::read("./jwtRS256.pub.key").unwrap();

fn main () {
  // do some stuff
}

However, I am getting a compiler error as follows:

error[E0015]: calls in constants are limited to constant functions, tuple structs and tuple variants
  --> src/main.rs:16:30
   |
16 | const PRIVATE_KEY: Vec<u8> = std::fs::read("./jwtRS256.key").unwrap();
   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

I understand; makes sense. The compiler wants to be able to evaluate the values of these constants at compile time and it can't. What is the alternative, though? I need those constants available all through my code, including in other modules. So, I cannot just move them as regular variables into the main function. What do I do instead? (I am on Rust 1.47.)

McKrassy
  • 961
  • 2
  • 10
  • 19

2 Answers2

4

THere are two possibilities and it's a bit unclear which one you want:

Embed the content of the files in the binary

include_bytes and include_str will read the files during compilation and embed their contents (according to the machine having compiled the binary) in the final program.

Runtime global

If you want to read the files when the program starts and make those contents available globally, you want lazy_static or once_cell instead, they allow running code to initialise a global at runtime.

const v static

static is probably what you want either way, it's much closer to globals in other languages.

const is more macro-ish, all references to the const's name will be replaced by the value, directly.

This is often what you want for e.g. simple numbers (that way they get compiled as immediates rather than loads from memory), but for more complex types or large amounts of data static is generally the better pick.

Masklinn
  • 34,759
  • 3
  • 38
  • 57
  • OP's example reads the files at runtime (if it worked). – Acorn Nov 09 '20 at 12:45
  • It's a bit unclear so I'll add a blurb about lazy_static / once_cell. – Masklinn Nov 09 '20 at 12:47
  • Also, it isn't true that `const` is "expanded". – Acorn Nov 09 '20 at 12:50
  • "constants are inlined wherever they're used, making using them identical to simply replacing the name of the const with its value" it's possible that the compiler is smart enough to deduplicate string literals, but I wouldn't really bet on it, and it seems completely unnecessary to rely on that. – Masklinn Nov 09 '20 at 12:52
  • Even if the OP's intent is to evaluate the constants at compile time, I wouldn't really recommend embedding private keys in the binary. – Sven Marnach Nov 09 '20 at 12:53
  • @SvenMarnach that's certainly true in general, though I could imagine something like a unikernel or somesuch doing that, so that it doesn't need any FS access or anything to run. – Masklinn Nov 09 '20 at 12:55
  • @Masklinn Semantically, perhaps, but the compiler does share the data without making copies. That is, it does not inline it. – Acorn Nov 09 '20 at 12:55
  • @Acorn It depends. Integer constants are usually inlined wherever they are used. And all of that is an implementation detail of the compiler, and can change at any time. – Sven Marnach Nov 09 '20 at 12:57
  • 1
    @SvenMarnach That is because integer constants are faster when inlined, but the compiler is not required to inline them. The answer claims this is not the case, which is wrong, because there is no such requirement on the implementation. – Acorn Nov 09 '20 at 12:58
  • @Acorn the official documentation for the `const` item literally states that `const` values are inlined. String literals are string literals so they're always moved to the data segment (though I'm not sure that's part of the language semantics either), and there's certainly no *language-level guarantee* that string literals are deduplicated. – Masklinn Nov 09 '20 at 13:10
  • 1
    @Masklinn No, the official reference does not say that. It says "*essentially* inlined" to explain the semantics, not the implementation. It clarifies that the addresses may not be the same (but may be), and they may or may not be stable. In short, it gives freedom to the implementation to do whatever is best. String literals can (and in many cases are) inlined in code if they are small enough and their address are not taken. Nobody is saying string literals are guaranteed to be shared or not shared. Claiming they are *never* shared is, however, wrong. – Acorn Nov 09 '20 at 15:25
  • @Acorn ""It says "essentially inlined" to explain the semantics, not the implementation." and semantics is the thing you're supposed to rely on. "Claiming they are never shared is, however, wrong." good thing nobody claimed that then. – Masklinn Nov 09 '20 at 16:54
  • @Masklinn "*semantics is the thing you're supposed to rely on*" Semantics do not constrain the implementation here. "*good thing nobody claimed that then.*" You did, several times, and your answer still does. – Acorn Nov 09 '20 at 17:10
  • @Acorn Can you defend your statement: *it isn't true that const is "expanded"*? That is, is there a detectable difference between expanding a `const` on each use and deduplicating them later, vs. evaluating it once and sharing the static by implicit reference (what I interpret you to be saying)? If not, this seems like a distinction without a difference. – trent Nov 09 '20 at 17:22
  • @trentcl I am not talking about that difference. The "expanded" bit was a quote from a previous edit of the answer. Masklinn claims `const` items are "macro-ish"/inlined/expanded and therefore they should not be used for large data etc – Acorn Nov 09 '20 at 18:36
  • @Acorn Yes, because that is correct, every use of a `const` is conceptually a distinct object. The compiler *may* combine them but it is misleading to say it *does* combine them. This is why we have variations of [this question](https://stackoverflow.com/q/50631189/3650362) asked every few months. – trent Nov 09 '20 at 19:00
  • @trentcl I have not said it combines them nor that they are conceptually the same one. – Acorn Nov 10 '20 at 09:35
2

If these files are present and compile-time and your only goal is to include them in the compilation, then you can simply use include_bytes!("./jwtRS256.key"). However, if you want to read these files at runtime, then consider using lazy_static:

lazy_static! {
    pub static ref PRIVATE_KEY: Vec<u8> = std::fs::read("./jwtRS256.key").unwrap();
}

This crate essentially allows you to lazily initialize static variables and use them anywhere.

Aplet123
  • 33,825
  • 1
  • 29
  • 55