Variables
A variable is a name that holds a value or object. All variables in Mojo are
mutable—their value can be changed. (If you want to define a constant value that
can't change at runtime, see the
alias keyword.)
When you declare a variable in Mojo, you allocate a logical storage location, and bind a name to that storage location.
var greeting: String = "Hello World"The var statement above does three things:
- It declares a logical storage location (in this case, a storage location sized
to hold a
Stringstruct). - It binds the name
greetingto this logical storage location. - It initializes the storage space with a newly-created
Stringvalue, with the text, “Hello World”. The new value is owned by the variable. No other variable can own this value unless we intentionally transfer it.
Variable declarations
Mojo has two ways to declare a variable:
-
Explicitly-declared variables are created with the
varkeyword.var a = 5 var b: Float64 = 3.14 var c: String -
Implicitly-declared variables are created the first time the variable is used, either with an assignment statement, or with a type annotation:
a = 5 b: Float64 = 3.14 c: String
Both types of variables are strongly typed—the type is either set explicitly with a type annotation or implicitly when the variable is first initialized with a value.
Either way, the variable receives a type when it's created, and the type never changes. So you can't assign a variable a value of a different type:
count = 8 # count is type Int
count = "Nine?" # Error: can't implicitly convert 'StringLiteral' to 'Int'Some types support implicit conversions from other types. For example, an integer value can implicitly convert to a floating-point value:
var temperature: Float64 = 99
print(temperature)99.0In this example, the temperature variable is explicitly typed as Float64,
but assigned an integer value, so the value is implicitly converted to a
Float64.
Implicitly-declared variables
You can create a variable with just a name and a value. For example:
name = "Sam"
user_id = 0Implicitly-declared variables are strongly typed: they take the type from the
first value assigned to them. For example, the user_id variable above is type
Int, while the name variable is type String. You can't assign a string to
user_id or an integer to name.
You can also use a type annotation with an implicitly-declared variable, either as part of an assignment statement, or on its own:
name: String = "Sam"
user_id: IntHere the user_id variable has a type, but is uninitialized.
Implicitly-declared variables are scoped at the function level. You create an implicitly-declared variable the first time you assign a value to a given name inside a function. Any subsequent references to that name inside the function refer to the same variable. For more information, see Variable scopes, which describes how variable scoping differs between explicitly- and implicitly-declared variables.
Explicitly-declared variables
You can declare a variable with the var keyword. For example:
var name = "Sam"
var user_id: IntThe name variable is initialized to the string "Sam". The user_id variable
is uninitialized, but it has a declared type, Int for an integer value.
Since variables are strongly typed, you can't assign a variable a value of a different type, unless those types can be implicitly converted. For example, this code will not compile:
var user_id: Int = "Sam"Explicitly-declared variables follow lexical scoping, unlike implicitly-declared variables.
Type annotations
Although Mojo can infer a variable type from the first value assigned to a variable, it also supports static type annotations on variables. Type annotations provide a more explicit way of specifying the variable's type.
To specify the type for a variable, add a colon followed by the type name:
var name: String = get_name()
# Or
name: String = get_name()This makes it clear that name is type String, without knowing what the
get_name() function returns. The get_name() function may return a String,
or a value that's implicitly convertible to a String.
If a type has a constructor with just one argument, you can initialize it in two ways:
var name1: String = "Sam"
var name2 = String("Sam")
var name3 = "Sam"All of these lines invoke the same constructor to create a String from a
StringLiteral.
Late initialization
Using type annotations allows for late initialization. For example, notice here
that the z variable is first declared with just a type, and the value is
assigned later:
fn my_function(x: Int):
var z: Float32
if x != 0:
z = 1.0
else:
z = foo()
print(z)
fn foo() -> Float32:
return 3.14If you try to pass an uninitialized variable to a function or use it on the right-hand side of an assignment statement, compilation fails.
var z: Float32
var y = z # Error: use of uninitialized value 'z'Implicit type conversion
Some types include built-in type conversion (type casting) from one type into its own type. For example, if you assign an integer to a variable that has a floating-point type, it converts the value instead of giving a compiler error:
var number: Float64 = Int(1)
print(number)1.0As shown above, value assignment can be converted into a constructor call if the target type has a constructor that meets the following criteria:
-
It's decorated with the
@implicitdecorator. -
It takes a single required argument that matches the value being assigned.
So, this code uses the Float64 constructor that takes an
integer: __init__(out self, value: Int).
In general, implicit conversions should only be supported where the conversion is lossless.
Implicit conversion follows the logic of overloaded functions. If the destination type has a viable implicit conversion constructor for the source type, it can be invoked for implicit conversion.
So assigning an integer to a Float64 variable is exactly the same as this:
var number = Float64(1)Similarly, if you call a function that requires an argument of a certain type
(such as Float64), you can pass in any value as long as that value type can
implicitly convert to the required type (using one of the type's overloaded
constructors).
For example, you can pass an Int to a function that expects a Float64,
because Float64 includes an implicit conversion constructor that takes an
Int:
fn take_float(value: Float64):
print(value)
fn pass_integer():
var value: Int = 1
take_float(value)For more details on implicit conversion, see Constructors and implicit conversion.
Variable scopes
Variables declared with var are bound by lexical scoping. This
means that nested code blocks can read and modify variables defined in an
outer scope. But an outer scope cannot read variables defined in an
inner scope at all.
For example, the if code block shown here creates an inner scope where outer
variables are accessible to read/write, but any new variables do not live
beyond the scope of the if block:
def lexical_scopes():
var num = 1
var dig = 1
if num == 1:
print("num:", num) # Reads the outer-scope "num"
var num = 2 # Creates new inner-scope "num"
print("num:", num) # Reads the inner-scope "num"
dig = 2 # Updates the outer-scope "dig"
print("num:", num) # Reads the outer-scope "num"
print("dig:", dig) # Reads the outer-scope "dig"
lexical_scopes()num: 1
num: 2
num: 1
dig: 2Note that the var statement inside the if creates a new variable with
the same name as the outer variable. This prevents the inner loop from accessing
the outer num variable. (This is called "variable shadowing," where the inner
scope variable hides or "shadows" a variable from an outer scope.)
The lifetime of the inner num ends exactly where the if code block ends,
because that's the scope in which the variable was defined.
This is in contrast to implicitly-declared variables (those without the var
keyword), which use function-level scoping (consistent with Python variable
behavior). That means, when you change the value of an implicitly-declared
variable inside the if block, it actually changes the value for the entire
function.
For example, here's the same code but without the var declarations:
def function_scopes():
num = 1
if num == 1:
print(num) # Reads the function-scope "num"
num = 2 # Updates the function-scope variable
print(num) # Reads the function-scope "num"
print(num) # Reads the function-scope "num"
function_scopes()1
2
2Now, the last print() function sees the updated num value from the inner
scope, because implicitly-declared variables (Python-style variables) use function-level
scope (instead of lexical scope).
Copying and moving values
Remember that a variable owns its value, and only one variable can own a given value at a time. To take this one step further, you can think of an assignment statement as assigning ownership of a value to a variable:
owning_variable = "Owned value"This means the value on the right-hand side of the assignment statement must be transferrable to the new variable. Here's an example where that doesn't work:
first = [1, 2, 3]
second = first # error: 'List[Int]' is not implicitly copyable because it does
# not conform to 'ImplicitlyCopyable'The first assignment is no problem: the expression [1, 2, 3] creates a new
List value that doesn't belong to any variable, so its ownership can be
transferred directly to the first variable.
But the second assignment causes an error. Since the List is owned by the
first variable, it can't simply be transferred to the second variable
without an explicit signal from the user. Does the user want to transfer the
value from first to second? Or create a copy of the original value?
These choices depend on some features of the type of the values involved: specifically, if the values are movable, copyable, or implicitly copyable.
-
A copyable type can be copied explicitly, by calling its
copy()method.second = first.copy()This leaves
firstunchanged and assignssecondits own, uniquely owned copy of the list. -
An implicitly copyable type can be copied without an explicit signal from the user.
one_value = 15 another_value = one_value # implicit copyHere
one_valueis unchanged, andanother_valuegets a copy of the value.Implicitly copyable types are generally simple value types like
Int,Float64, andBoolwhich can be copied trivially. -
The ownership of a value can be be explicitly transferred from one variable to another by appending the transfer sigil (
^) after the value to transfer:second = first^This moves the value to
second, and leavesfirstuninitialized.In many cases, this ownership transfer also involves moving the value from one memory location to another, which requires the value to be either movable or copyable.
You don't have to digest all of these details now: copyability and movabiltiy are discussed in more detail in the section on making structs copyable and movable and in the section on the value lifecycle.
Reference bindings
Some APIs return references to values owned elsewhere. References can be useful to avoid copying values. For example, when you retrieve a value from a collection, the collection returns a reference, instead of a copy.
animals: List[String] = ["Cats", "Dogs, "Zebras"]
print(animals[2]) # Prints "Zebras", does not copy the value.But if you assign a reference to a variable, it creates a copy (if the value is implicitly copyable) or produces an error (if it isn't).
items = [99, 77, 33, 12]
item = items[1] # item is a copy of items[1]
item += 1 # increments item
print(items[1]) # prints 77To hold on to a reference, use the ref keyword to create a reference binding:
ref item_ref = items[1] # item_ref is a reference to item[1]
item_ref += 1 # increments items[1]
print(items[1]) # prints 78Here the name item_ref is bound to the reference to items[1]. All reads and
writes to item_ref go to the referenced item.
Reference bindings can also be used when iterating through collections with
for loops.
Once a reference binding is assigned, it can't be re-bound to a different location. For example:
ref item_ref = items[2] # error: invalid redefinition of item_refFor more information on references, see Working with references.
Was this page helpful?
Thank you! We'll create more content like this.
Thank you for helping us improve!