Create a vector of constants, outside main

This is probably just bad habits learned from other languages, but I would like to create a "vector of constants" such that I can later call them individually by index.

I can get this to work

const KBA: f64 = 0.36;
const KBO: f64 = 0.31;

but if I try this

let mut con::Vec<f64> = [18.0,13.0,8.0,0.32,0.36,0.31];

I get the "expected item" error.

Is there a simple way to do this, on one line or is it not possible?

Thx. J

Have you seen the vec! macro?
Also your code has an additional colon, hence the expected item error. Instead it can be written like this:

let mut con: Vec<f64> = vec![18.0,13.0,8.0,0.32,0.36,0.31];

Most Rust code is not allowed outside of functions. You can't use let at the top level.

You can make global variables with static, but they can be initialized only with compile-time constants, and non-empty Vec can't be made at compile time.

You can make arrays:

static A: [f64; 6] = [18.0,13.0,8.0,0.32,0.36,0.31];
6 Likes

Take into account:

struct Constants {
    kba: f64,
    kbu: f64,
}

const CON: Constants = Constants { kba: 0.36, kbu: 0.31 };

I was wondering if this is better or worse:

static A: &[f64] = &[18.0,13.0,8.0,0.32,0.36,0.31];

They seem to make the same assembly, so I'm guessing it doesn't matter. Right...? I'm 60% confident.

1 Like

What's nice about an array is you can choose to treat it as an array with a const size, or as a slice with a simple coercion. If you declare it as a slice, you can't treat it as an array.

The code

static A: &[f64] = &[18.0,13.0,8.0,0.32,0.36,0.31];

is logically equivalent to

static UNNAMED_STATIC_1: [f64; 6] = [18.0,13.0,8.0,0.32,0.36,0.31];
static A: &[f64] = &UNNAMED_STATIC_1;

via promotion, which happens when you use & (or otherwise borrow a value) in a constant context. So, the it would only make a difference if:

  • you take the address of A itself, which is now distinct from what A points to,
  • you want to make a copy of the array (can’t do that when the size is erased), or
  • you care about the type being a slice type (no size defined by the type) instead of an array value (size defined to be 6) for any other reason.
1 Like

thanks but just throws the same error

   |
39 | let mut con:Vec<f64> = vec![18.0,13.0,8.0,0.32,0.36,0.31];
   | ^^^ expected item
```   |

What is the content of line 38?
I suppose you had forgotten a semicolon.

Outside of main, you cannot use let bindings.

error: expected item, found keyword `let`
 --> src/…
  |
5 | let mut con: Vec<f64> = [18.0,13.0,8.0,0.32,0.36,0.31];
  | ^^^ expected item
  |
  = note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>

As the full error message is pointing out, “expected item” is referring to the syntactical notion of an “item” as defined in the reference.

More concretely, the kind of items you’ll be interested in for defining some global value/variable will be static and const. Let’s not discuss the difference between those two in detail here; but both do have in common that their (initial) value must be a “constant”, evaluated at compile-time. And compile-time evaluation won’t support allocating a Vec:

static CON: Vec<f64> = vec![18.0,13.0,8.0,0.32,0.36,0.31];
error[E0010]: allocations are not allowed in statics
 --> src/…
  |
5 | static CON: Vec<f64> = vec![18.0,13.0,8.0,0.32,0.36,0.31];
  |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ allocation not allowed in statics
  |
  = note: this error originates in the macro `vec` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0015]: cannot call non-const fn `slice::<impl [f64]>::into_vec::<std::alloc::Global>` in statics
 --> src/…
  |
5 | static CON: Vec<f64> = vec![18.0,13.0,8.0,0.32,0.36,0.31];
  |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: calls in statics are limited to constant functions, tuple structs and tuple variants
  = note: consider wrapping this expression in `std::sync::LazyLock::new(|| ...)`
  = note: this error originates in the macro `vec` (in Nightly builds, run with -Z macro-backtrace for more info)

A general solution can be to define a global value in a static variable that is initialized lazily, lifting the need to do (or other disallowed operations) allocations at compile-time. Since recently, the relevant wrapper type, now called LazyLock, is even part of the standard library (and you’ll see it mentioned in the compiler error above):

use std::sync::LazyLock;
static CON: LazyLock<Vec<f64>> = LazyLock::new(|| vec![18.0,13.0,8.0,0.32,0.36,0.31]);

However, for lists of constants, it is usually a nicer approach to just avoid Vec and allocations entirely; as other answers already demonstrated, using [f64; 6] or &[f64] can be a good and simple solution here.

1 Like

Thanks for this, works nicely. I still have a lot to learn about Rust theory, no idea why it is such a big job to create something like this outside the main function.

Rust is lower-level than many other languages. In Rust, the main function very closely represents the entirety of what your program is actually doing. You don’t mention concretely which other languages you are comparing to, but values outside of main need to be computed at some point.

Rust allows such values to have constant values; the value is determined by the compiler and “baked into” your executable, so it loads with your program code when it starts.

Using LazyLock in Rust can be a possible workaround to these limitations. The static global variable gets initialized with some dummy/null-like value and a function pointer that defines how the actual value can be computed, and every access to the inner value is then implemented as a “initialize first if it wasn’t already” kind of step; and this is made quite convenient (and mostly implicit) by doing this through a Deref implementation. Still, this happens in code that you, call from within your main function.

Other languages do it differently; they (e.g. Java) could have static initializers that run when your program starts, before main gets called. Yet again, other languages are not compiled at all, so everything happens at run-time anyways, and global variables can be initialized however you like (this kind of computation would also happen before any main function is called; some interpreted languages don’t even have any formalized notion of main function in the first place).

1 Like

It's an arbitrary design decision. Rust is following the design of the C family of languages, where code runs in main(), and not outside of it. Scripting languages tend to allow code anywhere, but Rust isn't a scripting language.

1 Like

Also true of some functional languages.

I've noticed the error message for global let should have been more informative, but it wasn't accounting for use of let mut.

5 Likes