Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

env! for non-string constants #1907

Closed
jmegaffin opened this issue Feb 17, 2017 · 10 comments
Closed

env! for non-string constants #1907

jmegaffin opened this issue Feb 17, 2017 · 10 comments
Labels
T-libs-api Relevant to the library API team, which will review and decide on the RFC.

Comments

@jmegaffin
Copy link

jmegaffin commented Feb 17, 2017

There are some useful environment variables that are or could be non-string values, like CARGO_PKG_VERSION_*, NUM_JOBS, and DEP_*_*. Currently, however, they are only exposed to Rust as constant strings, not numbers, so it is impossible to do something like:

pub const VERSION_MAJOR: usize = env!("CARGO_PKG_VERSION_MAJOR");

You need to parse the string into an integer and then unwrap the result, which are both non-constant operations. If there were env!-like macros for boolean, integral, and fractional values and characters, then it would be possible to use them in new places like constant definitions, patterns, and eventually generics. It is possible to work around this in some cases by defining functions instead that internally parse the string into the desired type and using comparisons instead of pattern matching, but this is not ideal for ergonomics and potentially efficiency.


Of course, this would also be satisfied by making the appropriate standard library functions (str::parse, Option::unwrap) const fns, which would work for even user-defined types, but the macro solution could be implemented immediately without having to change any existing APIs. These macros could also be implemented as procedural macros, but support for this feature outside of deriving traits is poor as of the time of writing.

@solson
Copy link
Member

solson commented Feb 18, 2017

Of course, this would also be satisfied by making the appropriate standard library functions (str::parse, Option::unwrap) const fns, which would work for even user-defined types

I glanced at the implementation of parse for integers and it seems likely that Miri-powered constant expressions could handle it. It's going to take a while to iron out all the design and implementation details for that, though.

@Ericson2314
Copy link
Contributor

On a meta note, improved env! + miri is an interesting first case on looking at the division of labor between procedural macros and constant evaluation. Ultimately, only the host platform (of the code being built) / Miri know what a usize is. But parsing the env var into a literal that may or may not overflow can be done on build platform / with proc macros.

@jmegaffin
Copy link
Author

But parsing the env var into a literal that may or may not overflow can be done on build platform / with proc macros.

Yes, this is precisely what I mean.

@Centril Centril added the T-libs-api Relevant to the library API team, which will review and decide on the RFC. label Feb 10, 2018
@Centril
Copy link
Contributor

Centril commented Oct 8, 2018

This should simply "fall out of" CTFE improvements so there's nothing special about env! here. Therefore I'll close it since it's not directly actionable.

@Centril Centril closed this as completed Oct 8, 2018
@mcclure
Copy link

mcclure commented Mar 23, 2021

This was closed in 2018, but CTFE appears to be in now (?) and if I try this (I thought, very reasonable) code:

const rovr_version:(Option<i64>,Option<i64>,Option<i64>) =
	(env!("CARGO_PKG_VERSION_MAJOR"),
	 env!("CARGO_PKG_VERSION_MINOR"),
	 env!("CARGO_PKG_VERSION_PATCH")).map( |s| s.parse::<i64>().ok() );

I get:

error[E0015]: calls in constants are limited to constant functions, tuple structs and tuple variants
  --> src/core/mod.rs:8:2
   |
8  |       (env!("CARGO_PKG_VERSION_MAJOR"),
   |  _____^
9  | |      env!("CARGO_PKG_VERSION_MINOR"),
10 | |      env!("CARGO_PKG_VERSION_PATCH")).map( |s| s.parse::<i64>().ok() );
   | |______________________________________________________________________^

(I had to get the tuple crate and use tuple::Map to be able to map over a tuple, but that's not why I get the error, this doesn't work either:)

const blgrf:Option<i64> =
	"FMERNF".parse::<i64>().ok();
error[E0015]: calls in constants are limited to constant functions, tuple structs and tuple variants
  --> src/core/mod.rs:13:2
   |
13 |     "FMERNF".parse::<i64>().ok();
   |     ^^^^^^^^^^^^^^^^^^^^^^^

Through a chain of individually reasonable technical decisions, Rust has wound up in a situation where there is no way to get compile-time access to your own version number (as set in the language's own official package manager system) as an integer. That seems "obviously" weird, and the fix— even more CTFE improvements, something like #20— does not even seem to have a current RFC, so it could be literally years more before there is progress. Is there really nothing "actionable" here?

@luser
Copy link

luser commented Mar 23, 2021

Making const fns work with generics is covered by #2632. (str::parse is generic over types that implement FromStr.) That specific trait is covered by rust-lang/rust#59133 .

@steveklabnik
Copy link
Member

steveklabnik commented Mar 24, 2021

Is there really nothing "actionable" here?

@mcclure a small procedural note; issues on this tracker are mostly "a place for anyone to say some thoughts on a topic that anyone else may be interested in," it doesn't play any part in the actual process of changing things in Rust. An issue here being open or closed doesn't mean anything with regards to Rust feature planning, tracking, or anything else.

So, in this specific case, yes, there very may well be some things to be done, as @luser mentions, but that doesn't mean this issue particularly needs to be open or closed as part of it.

@eddyb
Copy link
Member

eddyb commented Mar 24, 2021

This was closed in 2018, but CTFE appears to be in now (?)

For some background, Rust had consteval before 1.0, though if we mean const fn, or the current miri consteval, that's a bit more recent (still older than 2018 IIRC).

What the closing message meant by "CTFE improvements" is "future additions to CTFE" (e.g. trait impls with const fn methods), and they're still in the future (though we have a better idea of what they might look like).

The whole story is a bit sad, really - miri itself is really powerful compared to what we currently let you do without e.g. de-traitifying various algorithms.

In a sense, Rust has to do all of the implementation legwork e.g. C++ would have to (which has been done in terms of miri, and most of it years ago), but on top of that we have to design systems that let us check correct use of abstractions without breaking their boundary and exposing the insides directly (as e.g. C++ does).

Also, most of the work on CTFE (and more recently, const generics as well) has been driven by unpaid volunteers, which can only be so rapid-paced and comprehensive.
(Not to diminish their great work, but Rust just can't move at the pace of corporate projects like Swift which can have a lot more resources poured into them)

so it could be literally years more before there is progress

Indeed it could (given everything I've just said). But it's not a significant problem since you have at least 3 options:

  • use a proc macro that behaves like env! but parsing the result
    • this is a bit sketchy since rustc has no way to track the dependence on the actual env vars, but I doubt that matters in practice for now
  • use some hand-rolled code, if it really needs to be a compile-time constant
    • the version number doesn't end up in a type, does it?
  • use lazy_static! (or even thread_local! in more "interesting" cases)
    • this also lets you far more, such as having "constants" that actually can be overriden at runtime with e.g. env vars
      • though at that point a context type may be warranted instead

One last thing I'll say is that Cargo version strings don't have to be triples of integers, you can have pre-release versions, so the concept of parsing it has limited usefulness, and is best kept as an opaque string if possible.

@petrochenkov
Copy link
Contributor

@eddyb

this is a bit sketchy since rustc has no way to track the dependence on the actual env vars

It has!
See rust-lang/rust#74690.

@rodrimati1992
Copy link

rodrimati1992 commented Mar 27, 2021

I wrote the konst crate with some const equivalents of std functions, here is how you'd do what the code in the original comment tries to do:

use konst::{primitive::parse_usize, result::unwrap_ctx};

pub const VERSION_MAJOR: usize = unwrap_ctx!(parse_usize(env!("CARGO_PKG_VERSION_MAJOR")));

According to the cargo reference the prerelease version of a package is separate from the patch version.
"1.2.3-pre" gets you these env vars(tried it with a local crate to check):

  • CARGO_PKG_VERSION_MAJOR=1
  • CARGO_PKG_VERSION_MINOR=2
  • CARGO_PKG_VERSION_PATCH=3
  • CARGO_PKG_VERSION_PRE=pre

this means that parsing the patch version will give you an integer even in prerelease versions

bors added a commit to rust-lang-ci/rust that referenced this issue Mar 30, 2024
Make {integer}::from_str_radix constant

This commit makes FromStr on integers constant so that `const x: u32 = "23".parse();` works. More practical use-case is with environment variables at build time as discussed in rust-lang/rfcs#1907.

Tracking issue rust-lang#59133.

ACP: rust-lang/libs-team#74
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-libs-api Relevant to the library API team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests

10 participants