What are the differences between constants declared with const and immutable variables declared with Let in Rust?

Asked

Viewed 144 times

3

The declaration of immutable variables

In Rust if you want to declare a variable, we use the keyword let.

Example:

fn main() {
    let site_name = "Stack Overflow em Português";
    println!("{}",site_name);
}

Upshot:

Stack Overflow em Português

This kind of statement creates a immutable variable which, when amended, gives rise to errors.

Example:

fn main() {
    let site_name = "Stack Overflow em Português";
    println!("{}",site_name);
    site_name = "Stack Overflow em Português META";
    println!("{}",site_name);
}

Raises this error:

error[E0384]: cannot assign twice to immutable variable `site_name`
 --> src/main.rs:4:5
  |
2 |     let site_name = "Stack Overflow em Português";
  |         ---------
  |         |
  |         first assignment to `site_name`
  |         help: consider making this binding mutable: `mut site_name`
3 |     println!("{}",site_name);
4 |     site_name = "Stack Overflow em Português META";
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot assign twice to immutable variable

error: aborting due to previous error

For more information about this error, try `rustc --explain E0384`.

While executing rustc --explain E0384, it is explained that by default variables in Rust are immutable. To correct this error, one must add keyword mut after let when declaring the variable. See more.

The declaration of constants

Rust also has a specialized keyword for declaring constants, const.

Example:

fn main() {
    const SITE_NAME: &str = "Stack Overflow em Português";
    println!("{}",SITE_NAME);
    
}

Upshot:

Stack Overflow em Português

As with the example of immutable variables, the attempt of constant retribution will generate errors:

Example:

fn main() {
    const SITE_NAME: &str = "Stack Overflow em Português";
    println!("{}",SITE_NAME);
    SITE_NAME = "Stack Overflow em Português META";
    println!("{}",SITE_NAME);        
}

Upshot:

error[E0070]: invalid left-hand side of assignment
 --> src/main.rs:4:15
  |
4 |     SITE_NAME = "Stack Overflow em Português META";
  |     --------- ^
  |     |
  |     cannot assign to this expression

error: aborting due to previous error

For more information about this error, try `rustc --explain E0070`.

When executing rustc --explain E0070, The explanation arises of which left side of an assignment operator should be a place expression. A place expression represents a memory location and can be a variable (with optional namespacing), a melt, an index expression, or a reference field. See more.

The question

Before the information gathered above I ask:

In Rust what are the differences between constants declared with const and immutable variables declared with let?

2 answers

3

TL;DR: Immutable variables declared with let are only common variables, except that they cannot be changed throughout the program. Constants, created with const, are a completely different type, with several other properties, being immutability only one of them.


The difference is that let creates a variable and const creates a constant. It seems obvious, but the difference is important, as they are two different types.

According to the official book of the language, there are 4 differences between constants and variables, which are:

1. You cannot use the keyword mut in constants.

As noted in his examples, constants are at all times immutable

2. The type of the variable created using const should always be noted while using let you can let the compiler infer

For example, the following code fails to compile:

fn main() {
    const a = 3;
}

Error:

error: missing type for `const` item
 --> src/main.rs:2:11
  |
2 |     const a = 3;
  |           ^ help: provide a type for the item: `a: i32`

Replace const for let, works

3. Constants can be declared in any scope

For example, it works:

const NAME: &str = "stackoverflow";

fn main() {
    println!("{}", NAME);
}

That’s not:

let name = "stackoverflow";

fn main() {
    println!("{}", name);
}

4. Constants can be defined only for a constant expression, not the result of a function call or any other value that could only be calculated at runtime.

To illustrate this last difference, we can take advantage of the previous example. Note that if we declare the constant outside the scope, we cannot use a function (which is known only at runtime). The following code does not compile:

const NAME: String = String::from("stackoverflow");

fn main() {
    println!("{}", NAME);
}

Simply because String::from is not known at compile time.

3


The keyword const defines a constant. On the other hand, let defines a variable.

About the place where the declaration occurs, constants can be declared in any scope, including the top-level (modules). Variables, however, are limited to blocks, such as functions.

In Rust, by default, any variable (i.e., a Binding defined with let) is immutable, which means it cannot be changed by the program. However the programmer can explicitly make it mutable by sufficing the statement let with the modifier mut, thus:

let mut name = String::from("SOpt");

Constants obviously do not allow the modifier mut. The statement itself const already implies that the value placed there must be immutable in any situation.

A constant is an optional name to any constant value, that is, the value will be inlined when the constant is used.

Since constants are nothing more than names at constant values, they can be used in any constant context. Variables, whether or not immutable, no.

During variable declaration with let, the omission of the explicit note of the type in favor of the compiler’s inference is valid. However, when declaring a constant, the declaration const requires that its Binding is explicitly annotated with the type of its value, even if the inference is possible.

About life time, where applicable, variables may have any lifespan. Constants, however, always need to have a static lifespan, that is to say, Lifetime 'static. In some cases, this life span may be implied as there is Lifetime elision. Thus, when annotating a constant with, for example,, &str, what actually happens is &'static str.

It is interesting not to confuse "immutable value" with "constant value". They are different things. A variable can be immutable or mutable. A constant is always immutable.


Since Rust is a compiled language, there is an important distinction to be made between variables and constants. The value of a variable can be determined statically or dynamically (through a computation, for example). On the other hand, constants can only be determined by guaranteed static mechanisms.

Thus, you cannot define a constant whose value comes from any computation (those, for example, that come from a function). This limitation is given in view of the fact that, in most scenarios, the compiler cannot determine whether the return value of a function is constant or not. There is how to relate this limitation to the famous Halting Problem, already brilliantly explained here.

The only exception to this rule are functions const. This means that a constant can be declared with a value that comes from a constant function. This is because this type of function is not any, since only one subset of return values are allowed by the compiler, making it able to determine whether the returned value is actually static.

It’s also important to discuss how Rust treats constants and variables at a slightly lower level.

  • Every variable is associated with a memory address. This means that every variable value, immutable or mutable, has a specific location in the computer’s memory. Variables can be moved and borrowed (Moved and borrowed, for those familiar with the English terms).

  • Constants, on the other hand, are not stored in memory, at least not in a guaranteed manner.

    Most of the time, it’s interesting to see a constant as a mere alias. That is why, at the beginning of this reply, quoting the book, he said that "a constant is an optional name at any constant value". This means that when the code refers to the constant, its value will be simply copied there, literally. This process, which occurs at compile time, is a type of inlining.

    In that sense, this:

    const SITE_NAME_ABBR: &'static str = "SOpt";
    
    fn main() {
        println!("{}", SITE_NAME_ABBR);
        println!("{}", SITE_NAME_ABBR);
    }
    

    After the inlining, it’s kind of like this:

    fn main() {
        println!("{}", "SOpt");
        println!("{}", "SOpt");
    }
    

    The need for constants to be static, therefore, becomes undoubted. If the value came from a nondeterministic computation, each time the constant was consumed, a different value would be produced. Take this absurd example:

    const INVALID_CONST: isize = random_int();
    
    fn main() {
        println!("{}", INVALID_CONST);
        println!("{}", INVALID_CONST);
    }
    

    After the inlining, would be something like the code below. It makes no sense to replace a constant with a function that can return different values to each call, so much so that the compiler does not allow the expression to be used as a constant.

    fn main() {
        println!("{}", random_int());
        println!("{}", random_int());
    }
    

Browser other questions tagged

You are not signed in. Login or sign up in order to post.