Перейти к содержимому

Что изменилось

Предыдущая глава была про хорошие новости. Эта — про то, обо что вы споткнётесь в первый день. Все сообщения об ошибках ниже настоящие: получены на Mojo 1.0, а не придуманы.

🐍 Python
print("привет")
🔥 Mojo
def main():
print("привет")

У скомпилированной программы должна быть точка входа — с неё начинается выполнение бинарника. Эту роль играет main().

error: expressions must not appear at file scope; move this into a function body
Что это значит

Вы написали исполняемый код прямо в файле, вне функции.

Как исправить

Оберните его в def main():.

🐍 Python
def add(a, b):
return a + b
🔥 Mojo
def add(a: Int, b: Int) -> Int:
return a + b

Без типа компилятор не знает ни сколько байт занимает значение, ни какие машинные инструкции для него генерировать.

error: argument type must be specified
Что это значит

У аргумента функции не указан тип.

Как исправить

Допишите аннотацию: def add(a: Int, b: Int) -> Int:.

Пожалуй, самая обидная потеря для питониста. f"..." в Mojo 1.0 не поддерживается, и ошибка при этом выглядит загадочно:

error: expected ')' in call argument list
  print(f"x = {x}")
         ^
Что это значит

Компилятор не знает про префикс f и видит просто идентификатор f, за которым непонятно почему идёт строка. Отсюда невнятное сообщение про закрывающую скобку.

Как исправить

Передавайте значения аргументами print — он сам расставит пробелы:

print("x =", x)

Либо соберите строку явно: print("x = " + String(x)).

🐍 Python
print(f"x = {x}")
name = f"{first} {last}"
🔥 Mojo
print("x =", x)
var name = first + " " + last

String(значение) превращает в строку что угодно: число, булево, свою структуру — если она умеет себя печатать.

Вот это отличие для русскоязычного читателя важнее, чем для англоязычного, и в большинстве учебников его проглатывают.

error: `len(String/StringSlice)` is not supported because Mojo strings are UTF-8 encoded, so a single length is ambiguous
Что это значит

Строки в Mojo хранятся в UTF-8. «Длина» строки «Привет» — это 12 байт, 6 кодовых точек или 6 видимых символов. Компилятор отказывается угадывать, что вы имели в виду.

Как исправить

Скажите явно: s.byte_length() — байты, len(s.codepoints()) — кодовые точки Unicode, len(s.graphemes()) — видимые символы.

То же самое со срезами: s[1:4] не работает, нужно указать единицу измерения.

strings.mojo
def main():
var s = "Привет"
# В Mojo нет одной «длины строки»: нужно сказать, что именно считать
print("байт:", s.byte_length())
print("кодовых точек:", len(s.codepoints()))
print("графем:", len(s.graphemes()))
# Срезать тоже нужно осознанно — по байтам или по символам
print("первые три символа:", s[codepoint=0:3])
print("первые четыре байта:", s[byte=0:4])
# Вместо f-строк — обычная передача аргументов в print
var count = len(s.codepoints())
print("в слове", s, "ровно", count, "букв")
Результат

байт: 12 кодовых точек: 6 графем: 6 первые три символа: При первые четыре байта: Пр в слове Привет ровно 6 букв

Обратите внимание на последние две строки вывода: четыре байта — это всего два русских символа, потому что каждая кириллическая буква в UTF-8 занимает два байта. В Python вы бы этого не заметили и однажды получили бы обрезанную на середине символа строку.

🐍 Python
last = items[-1]
🔥 Mojo
var last = items[len(items) - 1]

Проверка «а не отрицательный ли индекс» стоила бы ветвления при каждом обращении к элементу — а обращения к элементам как раз и стоят в самых горячих циклах.

note: constraint failed: negative indexing is not supported, use e.g. `x[len(x) - 1]`
Что это значит

Вы обратились к элементу по отрицательному индексу.

Как исправить

Компилятор прямо в сообщении показывает замену: x[len(x) - 1].

var a: Float64 = 1.5
var b: Int = 2
print(a + b) # ошибка
print(a + Float64(b)) # так правильно
error: invalid call to '__add__': value passed to 'rhs' cannot be converted from 'Int' to 'Float64'
Что это значит

Неявного расширения Int до Float64 в Mojo нет: компилятор не станет незаметно менять представление числа.

Как исправить

Преобразуйте явно: Float64(b).

Само деление при этом ведёт себя по-питоновски: 7 / 2 даёт 3.5, а 7 // 23.

error: classes are not supported yet
Что это значит

Ключевого слова class в Mojo 1.0 нет. Слово «yet» в сообщении намекает, что когда-нибудь появится, но сегодня его нет.

Как исправить

Используйте struct. Отличие принципиальное: структура не имеет динамической природы Python-класса — состав полей и методов известен компилятору целиком, менять его во время выполнения нельзя.

@fieldwise_init
struct Point(ImplicitlyCopyable):
var x: Int
var y: Int
def main():
var p = Point(1, 2)
print(p.x, p.y)

Самое непривычное. В Python b = a просто добавляет ссылку. В Mojo у значения один владелец, и присваивание — это либо копия, либо передача владения. Причём копию нужно разрешить явно:

error: value of type 'P' cannot be implicitly copied, it does not conform to 'ImplicitlyCopyable'
Что это значит

Вы присвоили структуру другой переменной, а её тип не разрешает неявное копирование.

Как исправить

Компилятор подсказывает оба выхода прямо в сообщении: передать владение через var b = a^ или скопировать явно через var b = a.copy(). Третий вариант — разрешить неявные копии, объявив структуру как struct P(ImplicitlyCopyable).

Это ядро языка, и ему посвящён целый раздел. Пока достаточно запомнить: если компилятор ругается на присваивание — он не придирается, а спрашивает, что вы имели в виду.

🐍 Python
def risky():
raise ValueError("плохо")
🔥 Mojo
def risky() raises:
raise Error("плохо")

Функция, которая может бросить исключение, помечается словом raises — и вызывающий код обязан это учитывать. В Python исключение может прилететь откуда угодно, в Mojo это часть контракта функции.

error: 'raise' requires a surrounding 'try' block or the enclosing function to declare 'raises'
Что это значит

Внутри функции есть raise, но сама функция не объявлена как raises.

Как исправить

Допишите raises в сигнатуру либо оберните код в try.

🐍 Python
f = lambda x: x + 1
🔥 Mojo
var f = lambda (x: Int) -> Int: x + 1

Аргументы — в скобках и с типами, возвращаемый тип указывается явно. Компилятор, кстати, сам подскажет правильную форму, если написать по-питоновски.

Присваивание без var пока работает, но компилятор предупреждает:

warning: implicit declaration of 'x' is deprecated; add 'var' before the assignment target
Что это значит

Неявное объявление переменных объявлено устаревшим. Сегодня это предупреждение, в будущих версиях станет ошибкой.

Как исправить

Пишите var x = 1 при первом присваивании. Дальше в теле функции var уже не нужен: x = 2 — это обычное изменение значения.

12. Программа компилируется, а не интерпретируется

Заголовок раздела «12. Программа компилируется, а не интерпретируется»
Окно терминала
uv run mojo main.mojo # собрать и сразу запустить
uv run mojo build main.mojo # получить исполняемый файл
./main # запустить его где угодно

Бинарнику не нужны ни интерпретатор, ни виртуальное окружение. Обратная сторона — компиляция занимает время, и «поправил строчку — запустил» уже не мгновенно.

В PythonВ Mojo 1.0
Код на верхнем уровнеТолько внутри def main()
def add(a, b)def add(a: Int, b: Int) -> Int
f"x = {x}"print("x =", x) или "x = " + String(x)
len(s) для строкиs.byte_length() / len(s.codepoints()) / len(s.graphemes())
s[1:4]s[byte=1:4] или s[codepoint=1:4]
items[-1]items[len(items) - 1]
1.5 + 21.5 + Float64(2)
class Foo:struct Foo:
b = a (ссылка)b = a^ (передача) или b = a.copy()
Исключения где угодноФункция помечается raises
lambda x: x + 1lambda (x: Int) -> Int: x + 1
x = 1var x = 1

🎯 Проверь себя

Почему в Mojo нельзя просто написать len(строки)?

Потому что для UTF-8 «длина» неоднозначна: у слова «Привет» 12 байт, 6 кодовых точек и 6 видимых символов. Компилятор отказывается выбирать за вас и требует явного byte_length(), codepoints() или graphemes().

Что делает `^` в строке `var b = a^`?

Передаёт владение значением от a к b. После этого пользоваться старой переменной нельзя — владелец у значения всегда один. Это альтернатива копированию, и она бесплатна.

Компилятор ругается на `1.5 + count`, где count имеет тип Int. Что не так?

Неявных числовых преобразований в Mojo нет. Нужно написать 1.5 + Float64(count). Это сделано специально: молчаливое изменение представления числа — источник трудноуловимых ошибок точности.

Ошибки компилятора вы теперь будете видеть часто — и это нормально. Следующая глава учит читать их быстро.

Примеры проверены на Mojo 1.0.0

Тексты курса — CC BY-NC-SA 4.0, код примеров — Apache 2.0