Variables and Mutability
In this chapter, we'll explore learn how variables work in mana and the difference between mutable and immutable bindings.
Variables and Mutability
By default, variables in Mana are immutable. This is one of many nudges Mana gives you to write your code in a way that takes advantage of the safety and easy concurrency that Mana offers.
Let's explore how and why Mana encourages you to favor immutability and when you might want to opt out.
Immutable Variables
When a variable is immutable, once a value is bound to a name, you can't change that value. Create a new project called variables:
mana new variables
cd variablesOpen src/main.mana and replace its code:
module main
fn main() -> void {
let x = 5
println("The value of x is: ", x)
x = 6
println("The value of x is: ", x)
}Save and run with mana run. You'll see an error message:
error: cannot assign twice to immutable variable `x`
--> src/main.mana:4:5
|
2 | let x = 5
| - first assignment
...
4 | x = 6
| ^^^^^ cannot assign twice to immutable variable
This error shows that the compiler helps you find bugs in your program. Compile-time errors may be frustrating, but they mean Mana is helping you catch bugs before your code runs.
Mutable Variables
Mutability can be useful and make code more convenient. Add mut in front of the variable name to make it mutable:
module main
fn main() -> void {
let mut x = 5
println("The value of x is: ", x)
x = 6
println("The value of x is: ", x)
}Now the program runs successfully:
The value of x is: 5
The value of x is: 6
When we use mut, we indicate that the value of x can change. The decision to use mutability is up to you and depends on what you think is clearest in that particular situation.
Constants
Like immutable variables, constants are values bound to a name that are not allowed to change. However, there are a few differences.
First, you aren't allowed to use mut with constants—they're always immutable.
Second, you declare constants using the const keyword, and the type must be annotated:
const THREE_HOURS_IN_SECONDS: int = 60 * 60 * 3Constants can be declared in any scope, including the global scope, making them useful for values that many parts of code need to know about.
The last difference is that constants may only be set to a constant expression, not the result of a value computed at runtime.
Naming Convention
Mana's naming convention for constants is to use all uppercase with underscores between words:
const MAX_POINTS: int = 100_000
const PI: f64 = 3.14159265359
const APPLICATION_NAME: string = "My App"Shadowing
You can declare a new variable with the same name as a previous variable. We say the first variable is shadowed by the second, meaning the second variable is what appears when the variable name is used.
module main
fn main() -> void {
let x = 5
let x = x + 1
{
let x = x * 2
println("The value of x in the inner scope is: ", x)
}
println("The value of x is: ", x)
}This program first binds x to 5. Then it creates a new variable x by adding 1, so x becomes 6. The inner scope shadows x with a value of 12. When that scope ends, the inner shadowing ends, and x returns to being 6.
Output:
The value of x in the inner scope is: 12
The value of x is: 6
Shadowing vs. Mutation
Shadowing is different from marking a variable as mut. If we try to reassign without using let, we'll get a compile-time error. By using let, we can transform a value but have the variable be immutable after those transformations.
Another difference is that we can change the type of the value while reusing the same name:
let spaces = " "
let spaces = spaces.len()The first spaces is a string, and the second is a number. Shadowing lets us reuse the spaces name rather than creating different names like spaces_str and spaces_num.
If we tried this with mut, we'd get an error:
let mut spaces = " "
spaces = spaces.len() // Error: mismatched typesNow let's look at the Data Types available in Mana.