Why is `const fn` different from other “const” things?
My understanding is that Rust has const contexts wherein the expression needs to be a compile-time constant. Like in
const x = 1;
static y = 2;
A proposed feature is inline const, i.e.
let x = const { 1 + 2 } + fun();
Here, const { 1 + 2 } is guaranteed to be evaluated at compile-time or fully “constant folded” (in compiler-speak) to 3 since it’s a const context.
But const fn is not like that.
const fn cf { […] }
Such a function is guaranteed to be useable in a constant context. But it’s also useable in a regular context:
let z = cf();
Here cf() may be evaluated at runtime: it’s up to the compiler how to constant fold it (or not), just like with a regular function. So as far as runtime calls are concerned (when it is called in a regular context) they are just regular functions.
const fn gets a lot of restrictions put on it because it needs to be useable for both purposes.[1] Take reading a file as an example: unproblematic for a regular function, but what files will be available to both a const context and a regular context?
[1] Not only for that reason though, and some restrictions will be lifted in due time.
I have two concerns with this:
- “const” means “const context” except for
const fnwhere it just means that it can be used in a const context. A const context is very straightforward: this things needs to be a constant—it’s not just a compiler hint but a semantic guarantee. - This potentially adds confusion in both directions: someone introduced to
const fnfirst might think that a constant (const x) might just be a compiler hint. On the other hand, someone introduced toconst xfirst might think thatconst fnis reserved for const contexts only, and in turn they might get confused by the fact that it has restrictions put on it in order to make runtime calls make sense. - Other languages seem confused with their use of the keyword “const”. Apparently it means read-only in C? Rust’s interpretation is much more sensible, but a more uniform approach would have been even more helpful. C++ has
constexprfor may-be-compile-time-evaluated andconstevalfor must-be-compile-time evaluated; Rust on the other hand uses “const” for both, just in different syntactic contexts. - Const-only functions are potentially useful in their own right. Yes, just running regular code as per usual of course. But maybe some compile-time reflection could also be available to such functions? Things that don’t exist at runtime. And const-only functions could alleviate some of the current restrictions (maybe: I’m not a compiler writer). Take the prior concern about reading files: what files are both present at compile- and runtime? For compile-time only functions you just have to worry about files that are accessible in your project tree: those probably don’t exist at runtime, but that’s not a concern for you.
What if you tried on some horrible programming adjective/policeman:
constable fn f { […] }
This is a dual-purpose function: compile-time or runtime.
Meanwhile this is compile-time only:
const fn f { […] }
Postscript: Macros?
It seems that Rust’s most general compile-time facility is macros. Compile-time functions seem to be only for values, not for Code<Expr> things like try!(). And that’s fine. But there still seems to be a place for compile-time (only?) functions, like e.g. “precalculating a sine table”.
Postscript 2: Nod to versioning
I’m aware that Rust is strict with only allowing specifically const functions (not regular non-const functions) in const contexts. I have no problem with that.