Что изменилось
This content is not available in your language yet.
Предыдущая глава была про хорошие новости. Эта — про то, обо что вы споткнётесь в первый день. Все сообщения об ошибках ниже настоящие: получены на Mojo 1.0, а не придуманы.
1. Код не может лежать на верхнем уровне
Заголовок раздела «1. Код не может лежать на верхнем уровне»print("привет")def main(): print("привет")У скомпилированной программы должна быть точка входа — с неё начинается
выполнение бинарника. Эту роль играет main().
error: expressions must not appear at file scope; move this into a function body
Вы написали исполняемый код прямо в файле, вне функции.
Оберните его в def main():.
2. Типы аргументов обязательны
Заголовок раздела «2. Типы аргументов обязательны»def add(a, b): return a + bdef add(a: Int, b: Int) -> Int: return a + bБез типа компилятор не знает ни сколько байт занимает значение, ни какие машинные инструкции для него генерировать.
error: argument type must be specified
У аргумента функции не указан тип.
Допишите аннотацию: def add(a: Int, b: Int) -> Int:.
3. F-строк нет
Заголовок раздела «3. F-строк нет»Пожалуй, самая обидная потеря для питониста. f"..." в Mojo 1.0
не поддерживается, и ошибка при этом выглядит загадочно:
error: expected ')' in call argument list
print(f"x = {x}")
^Компилятор не знает про префикс f и видит просто идентификатор f,
за которым непонятно почему идёт строка. Отсюда невнятное сообщение
про закрывающую скобку.
Передавайте значения аргументами print — он сам расставит пробелы:
print("x =", x)
Либо соберите строку явно: print("x = " + String(x)).
print(f"x = {x}")name = f"{first} {last}"print("x =", x)var name = first + " " + lastString(значение) превращает в строку что угодно: число, булево,
свою структуру — если она умеет себя печатать.
4. У строки нет одной длины
Заголовок раздела «4. У строки нет одной длины»Вот это отличие для русскоязычного читателя важнее, чем для англоязычного, и в большинстве учебников его проглатывают.
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] не работает, нужно указать единицу измерения.
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 вы бы этого не заметили и однажды получили бы обрезанную на середине символа строку.
5. Отрицательных индексов нет
Заголовок раздела «5. Отрицательных индексов нет»last = items[-1]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].
6. Числа не смешиваются молча
Заголовок раздела «6. Числа не смешиваются молча»var a: Float64 = 1.5var b: Int = 2print(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 // 2 — 3.
7. Вместо классов — структуры
Заголовок раздела «7. Вместо классов — структуры»error: classes are not supported yet
Ключевого слова class в Mojo 1.0 нет. Слово «yet» в сообщении
намекает, что когда-нибудь появится, но сегодня его нет.
Используйте struct. Отличие принципиальное: структура не имеет
динамической природы Python-класса — состав полей и методов известен
компилятору целиком, менять его во время выполнения нельзя.
@fieldwise_initstruct Point(ImplicitlyCopyable): var x: Int var y: Int
def main(): var p = Point(1, 2) print(p.x, p.y)8. Копирование перестало быть бесплатным
Заголовок раздела «8. Копирование перестало быть бесплатным»Самое непривычное. В 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).
Это ядро языка, и ему посвящён целый раздел. Пока достаточно запомнить: если компилятор ругается на присваивание — он не придирается, а спрашивает, что вы имели в виду.
9. Исключения объявляются заранее
Заголовок раздела «9. Исключения объявляются заранее»def risky(): raise ValueError("плохо")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.
10. Лямбды пишутся строже
Заголовок раздела «10. Лямбды пишутся строже»f = lambda x: x + 1var f = lambda (x: Int) -> Int: x + 1Аргументы — в скобках и с типами, возвращаемый тип указывается явно. Компилятор, кстати, сам подскажет правильную форму, если написать по-питоновски.
11. Переменные объявляются через var
Заголовок раздела «11. Переменные объявляются через var»Присваивание без 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 + 2 | 1.5 + Float64(2) |
class Foo: | struct Foo: |
b = a (ссылка) | b = a^ (передача) или b = a.copy() |
| Исключения где угодно | Функция помечается raises |
lambda x: x + 1 | lambda (x: Int) -> Int: x + 1 |
x = 1 | var 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). Это сделано специально: молчаливое изменение
представления числа — источник трудноуловимых ошибок точности.
Что дальше
Заголовок раздела «Что дальше»Ошибки компилятора вы теперь будете видеть часто — и это нормально. Следующая глава учит читать их быстро.
Тексты курса — CC BY-NC-SA 4.0, код примеров — Apache 2.0