Переменные¶
Переменная - это имя, которое содержит значение или объект. Все переменные в Mojo являются изменяемыми — их значение может быть изменено. (Если вы хотите определить постоянное значение, которое не может изменяться во время выполнения, обратитесь к ключевому слову alias.)
Когда вы объявляете переменную в Mojo, вы выделяете логическое хранилище и привязываете к этому хранилищу имя.
var greeting: String = "Hello World"
Оператор var, приведенный выше, выполняет три действия:
- Он объявляет логическое место хранения (в данном случае, место хранения, размер которого соответствует строковой структуре).
- Он привязывает имя
greetingк этому логическому месту хранения. - Он инициализирует пространство памяти вновь созданным строковым значением с текстом “Hello World”. Новое значение принадлежит переменной. Никакая другая переменная не может владеть этим значением, если мы намеренно не перенесем его.
Объявления переменных¶
В Mojo есть два способа объявить переменную:
-
Переменные, объявленные явно, создаются с помощью ключевого слова
var.var a = 5 var b: Float64 = 3.14 var c: String -
Неявно объявленные переменные создаются при первом использовании переменной либо с помощью инструкции присваивания, либо с аннотацией типа:
a = 5 b: Float64 = 3.14 c: String
Оба типа переменных строго типизированы — тип задается либо явно с помощью аннотации к типу, либо неявно, когда переменная впервые инициализируется значением.
В любом случае переменная получает тип при создании, и этот тип никогда не меняется. Таким образом, вы не можете присвоить переменной значение другого типа:
count = 8 # count - это тип Int
count = "Nine?" # Ошибка: не удается неявно преобразовать 'StringLiteral' в 'Int'
Некоторые типы поддерживают неявные преобразования из других типов. Например, целое значение может неявно преобразовываться в значение с плавающей запятой:
var temperature: Float64 = 99
print(temperature)
99.0
В этом примере переменная температуры явно введена как Float64, но ей присвоено целое значение, поэтому значение неявно преобразуется в Float64.
Неявно объявленные переменные¶
Вы можете создать переменную, содержащую только имя и значение. Например:
name = "Sam"
user_id = 0
Неявно объявленные переменные строго типизированы: их тип определяется по первому присвоенному им значению. Например, указанная выше переменная user_id имеет тип Int, а переменная name - тип String. Вы не можете присвоить user_id строку или целое число name.
Вы также можете использовать аннотацию типа с неявно объявленной переменной либо как часть инструкции присваивания, либо самостоятельно:
name: String = "Sam"
user_id: Int
Здесь переменная user_id имеет тип, но неинициализирована.
Область действия неявно объявленных переменных определяется на уровне функции. Неявно объявленная переменная создается при первом присвоении значения заданному имени внутри функции. Любые последующие ссылки на это имя внутри функции ссылаются на ту же переменную. Для получения дополнительной информации см. раздел Области видимости переменных, в котором описывается различие в области видимости переменных, объявленных явно и неявно.
Явно объявленные переменные¶
Вы можете объявить переменную с помощью ключевого слова var. Например:
var name = "Sam"
var user_id: Int
Переменная name инициализируется строкой "Sam". Переменная user_id неинициализирована, но у нее объявлен тип Int для целого значения.
Поскольку переменные строго типизированы, вы не можете присвоить переменной значение другого типа, если только эти типы не могут быть преобразованы неявным образом. Например, этот код не будет скомпилирован:
var user_id: Int = "Sam"
Явно объявленные переменные соответствуют лексической области видимости, в отличие от неявно объявленных переменных.
Аннотации к типам¶
Хотя Mojo может определять тип переменной по первому значению, присвоенному переменной, он также поддерживает аннотации к статическим типам переменных. Аннотации к типам предоставляют более точный способ указания типа переменной.
Чтобы указать тип переменной, добавьте двоеточие, за которым следует название типа:
var name: String = get_name()
# Или
name: String = get_name()
Это дает понять, что name имеет тип String, не зная, что возвращает функция get_name(). Функция get_name() может возвращать строку или значение, которое неявно преобразуется в String.
Если у типа есть конструктор только с одним аргументом, вы можете инициализировать его двумя способами:
var name1: String = "Sam"
var name2 = String("Sam")
var name3 = "Sam"
Все эти строки вызывают один и тот же конструктор для создания строки из StringLiteral.
Поздняя инициализация¶
Использование аннотаций типов позволяет выполнять позднюю инициализацию. Например, обратите внимание, что переменная z сначала объявляется только с указанием типа, а значение присваивается позже:
fn my_function(x: Int):
var z: Float32
if x != 0:
z = 1.0
else:
z = foo()
print(z)
fn foo() -> Float32:
return 3.14
Если вы попытаетесь передать неинициализированную переменную в функцию или использовать ее в правой части инструкции присваивания, компиляция завершится ошибкой.
var z: Float32
var y = z # Ошибка: использование неинициализированного значения 'z'
Поздняя инициализация работает только в том случае, если переменная объявлена с указанием типа.
Неявное преобразование типов¶
Некоторые типы включают встроенное преобразование типов (type casting) из одного типа в другой. Например, если вы присваиваете целое число переменной, которая имеет тип с плавающей запятой, это преобразует значение, а не выдает ошибку компилятора:
var number: Float64 = Int(1)
print(number)
1.0
Как показано выше, присвоение значения может быть преобразовано в вызов конструктора, если у целевого типа есть конструктор, соответствующий следующим критериям:
-
Он оформлен с помощью
@implicit decorator. -
Он принимает единственный обязательный аргумент, который соответствует присваиваемому значению.
Итак, в этом коде используется конструктор Float64, который принимает целое число: __init__(out self, value: Int).
В общем, неявные преобразования должны поддерживаться только в том случае, если преобразование выполняется без потерь.
Неявное преобразование следует логике перегруженных функций. Если целевой тип имеет работоспособный конструктор неявного преобразования для исходного типа, он может быть вызван для неявного преобразования.
Таким образом, присвоение целого числа переменной Float64 в точности совпадает с этим:
var number = Float64(1)
Аналогично, если вы вызываете функцию, которой требуется аргумент определенного типа (например, Float64), вы можете передать любое значение при условии, что этот тип значения может быть неявно преобразован в требуемый тип (с использованием одного из перегруженных конструкторов типа).
Например, вы можете передать Int функции, которая ожидает Float64, потому что Float64 включает в себя конструктор неявного преобразования, который принимает Int:
fn take_float(value: Float64):
print(value)
fn pass_integer():
var value: Int = 1
take_float(value)
Переменные области видимости¶
Переменные, объявленные с помощью var, ограничены лексической областью видимости. Это означает, что вложенные блоки кода могут считывать и изменять переменные, определенные во внешней области видимости. Но внешняя область видимости вообще не может считывать переменные, определенные во внутренней области видимости.
Например, показанный здесь блок кода if создает внутреннюю область, в которой внешние переменные доступны для чтения/записи, но любые новые переменные не выходят за пределы области блока if:
def lexical_scopes():
var num = 1
var dig = 1
if num == 1:
print("num:", num) # Считывает внешнюю область "num"
var num = 2 # Создает новую внутреннюю область "num"
print("num:", num) # Считывает внутреннюю область "num"
dig = 2 # Обновляет внешнюю область "dig"
print("num:", num) # Считывает внешнюю область "num"
print("dig:", dig) # Считывает внешнюю область "dig"
num: 1
num: 2
num: 1
dig: 2
Обратите внимание, что оператор var внутри if создает новую переменную с тем же именем, что и внешняя переменная. Это предотвращает доступ внутреннего цикла к внешней переменной num. (Это называется "затенение переменной", когда переменная внутренней области скрывает или "затеняет" переменную из внешней области.)
Время жизни внутреннего num заканчивается именно там, где заканчивается блок кода if, потому что это область, в которой была определена переменная.
Это отличается от неявно объявленных переменных (без ключевого слова var), которые используют область видимости на уровне функций (в соответствии с поведением переменных Python). Это означает, что когда вы изменяете значение неявно объявленной переменной внутри блока if, это фактически изменяет значение для всей функции.
Например, вот тот же код, но без объявлений var:
def function_scopes():
num = 1
if num == 1:
print(num) # Считывает область действия функции "num"
num = 2 # Обновляет переменную области действия функции
print(num) # Считывает область действия функции "num"
print(num) # Считывает область действия функции "num"
1
2
2
Теперь функция print() видит обновленное значение num из внутренней области, потому что неявно объявленные переменные (переменные в стиле Python) используют область функционального уровня (вместо лексической области).
Копирование и перемещение значений¶
Помните, что переменная является владельцем своего значения, и только одна переменная может одновременно владеть данным значением. Чтобы сделать еще один шаг вперед, вы можете представить себе оператор присваивания как присвоение переменной права собственности на значение:
owning_variable = "Owned value"
Это означает, что значение в правой части инструкции присваивания должно быть доступно для переноса в новую переменную. Вот пример, где это не работает:
first = [1, 2, 3]
second = first # ошибка: 'List[Int]' не является неявно копируемым, потому что он не соответствует 'ImplicitlyCopyable'
Первое присвоение не представляет проблемы: выражение [1, 2, 3] создает новое значение списка, которое не принадлежит ни одной переменной, поэтому его право собственности может быть передано непосредственно первой переменной.
Но второе присвоение приводит к ошибке. Поскольку список принадлежит первой переменной, он не может быть просто передан во вторую переменную без явного сигнала от пользователя. Хочет ли пользователь перенести значение из первой переменной во вторую? Или создать копию исходного значения?
Эти варианты зависят от некоторых особенностей типа используемых значений: в частности, от того, являются ли значения перемещаемыми, копируемыми или неявно копируемыми.
-
Копируемый тип можно скопировать явно, вызвав его метод
copy().second = first.copy()Это оставляет
firstбез изменений и присваиваетsecondего собственную, уникальную копию списка. -
Неявно копируемый тип может быть скопирован без явного сигнала от пользователя.
one_value = 15 another_value = one_value # неявная копияЗдесь значение
one_valueостается неизменным, а значениеanother_valueполучает копию значения.Неявно копируемые типы - это, как правило, простые типы значений, такие как
Int,Float64иBool, которые можно скопировать простым способом. -
Право собственности на значение может быть явно передано из одной переменной в другую путем добавления символа передачи (
^) после передаваемого значения:second = first^При этом значение перемещается на второе место, а первое остается неинициализированным.
Во многих случаях такая передача прав собственности также включает перемещение значения из одной ячейки памяти в другую, что требует, чтобы значение было либо перемещаемым, либо копируемым.
Вам не нужно сейчас вдаваться во все эти подробности: возможность копирования и перемещения обсуждается более подробно в разделе, посвященном созданию структур, доступных для копирования и перемещения, и в разделе, посвященном жизненному циклу значений.
Привязки ссылок¶
Некоторые API-интерфейсы возвращают ссылки на значения, принадлежащие другим пользователям. Ссылки могут быть полезны, чтобы избежать копирования значений. Например, когда вы извлекаете значение из коллекции, коллекция возвращает ссылку, а не копию.
animals: List[String] = ["Cats", "Dogs, "Zebras"]
print(animals[2]) # Печатает "Zebras", не копирует значение.
Но если вы присваиваете ссылку на переменную, она создает копию (если значение неявно копируется) или выдает ошибку (если это не так).
items = [99, 77, 33, 12]
item = items[1] # item is a copy of items[1]
item += 1 # increments item
print(items[1]) # prints 77
Чтобы сохранить ссылку, используйте ключевое слово ref для создания привязки к ссылке:
ref item_ref = items[1] # item_ref is a reference to item[1]
item_ref += 1 # increments items[1]
print(items[1]) # prints 78
Здесь имя item_ref привязано к ссылке на items[1]. Все операции чтения и записи в item_ref ведут к элементу, на который имеется ссылка.
Привязки ссылок также могут использоваться при переборе коллекций с помощью циклов for.
Как только привязка ссылки назначена, она не может быть повторно привязана к другому местоположению. Например:
ref item_ref = items[2] # ошибка: неверное переопределение item_ref