Система типов
В Python есть int, и он бесконечный. Есть float, и он всегда двойной
точности. Об остальном заботится интерпретатор. В Mojo типы — это описание
того, что лежит в памяти, поэтому их больше и они строже.
Что доступно сразу
Заголовок раздела «Что доступно сразу»Без единого импорта:
| Тип | Что это | Пример |
|---|---|---|
Int | Целое размером в машинное слово, на 64-битной системе — 64 бита | var n = 42 |
Float64 | Число с плавающей точкой двойной точности | var x = 1.5 |
Bool | Логическое значение | var flag = True |
String | Строка в UTF-8 | var s = "Привет" |
SIMD | Вектор из нескольких чисел одного типа | SIMD[DType.float32, 4](1, 2, 3, 4) |
Tuple | Кортеж, элементы могут быть разных типов | var t = (1, "два", 3.0) |
List | Список значений одного типа | var xs: List[Int] = [1, 2, 3] |
Dict | Словарь | var d = Dict[String, Int]() |
InlineArray | Массив фиксированной длины, живёт на стеке | InlineArray[Int, 3](fill=0) |
Из стандартной библиотеки импортируются, например:
from std.collections.set import Setfrom std.collections.optional import OptionalЧисла: главное отличие от Python
Заголовок раздела «Числа: главное отличие от Python»Int — это не Int64
Заголовок раздела «Int — это не Int64»Звучит странно, но это два разных типа, и один в другой сам не превратится:
var x = 3 # Intvar y: Int64 = x # ошибка компиляцииvar z = Int64(x) # так правильноerror: cannot implicitly convert 'Int' value to 'Int64'
Int — это «целое машинного размера», зависящее от платформы.
Int64 — это «ровно 64 бита», размер фиксирован. Смысл разный,
поэтому автоматического преобразования нет.
Преобразуйте явно: Int64(x). Правило простое: Int — для обычной
арифметики и индексов, размерные типы (Int8, UInt8, Int32,
Int64, Float32…) — когда важен точный формат данных: работа
с файлами, сетью, GPU или экономия памяти.
Размерные типы переполняются молча
Заголовок раздела «Размерные типы переполняются молча»var small: Int8 = 127small += 1print(small) # -128Ошибки не будет: тип занимает один байт, 127 — его максимум, дальше счётчик заворачивается. В Python целые растут неограниченно, здесь — нет. Если значение может выйти за границы, берите тип пошире или проверяйте сами.
Ловушка с делением
Заголовок раздела «Ловушка с делением»Самое коварное место главы. Посмотрите внимательно:
def main(): print(7 / 2) # 3.5
var a = 7 var b = 2 print(a / b) # 33.5 3
Разница в том, что делится. В первой строке — литералы: компилятор
вычисляет их сам ещё на этапе сборки и получает дробное число. Во второй —
переменные типа Int, а деление двух Int даёт Int, дробная часть
отбрасывается.
Питониста это подловит обязательно: в Python / всегда даёт float.
print(Float64(a) / Float64(b)) # 3.5 — то, что вы ожидалиprint(a // b) # 3 — честное целочисленное делениеПравило на каждый день: нужна дробь — приводите к Float64 явно.
SIMD — тип, которого нет в Python
Заголовок раздела «SIMD — тип, которого нет в Python»SIMD расшифровывается как single instruction, multiple data: одна
инструкция процессора обрабатывает сразу несколько чисел. Именно ради
этого Mojo и требует современный процессор.
def main(): var vector = SIMD[DType.float32, 4](1, 2, 3, 4) print(vector * 2) print(vector.reduce_add())[2.0, 4.0, 6.0, 8.0] 10.0
В квадратных скобках указываются параметры — тип элемента и количество элементов. Оба должны быть известны на этапе компиляции, потому что от них зависит, какую машинную инструкцию сгенерировать.
Подробно — в главе SIMD с нуля.
Преобразования всегда явные
Заголовок раздела «Преобразования всегда явные»Int(3.9) # 3, дробная часть отбрасываетсяFloat64(7) # 7.0String(255) # "255"Int(String("42")) # 42, но может бросить исключениеПоследняя строка требует raises у вызывающей функции: строка может
оказаться не числом, и это честно отражено в типе операции.
total = 0total += 1.5 # молча станет floatvar total = 0total += Int(1.5) # решение принимаете выPython сам меняет тип переменной по ходу дела. В Mojo так нельзя: сначала объявили тип, дальше все преобразования пишете руками.
Всё вместе
Заголовок раздела «Всё вместе»def main() raises: # Int — целое машинное слово, на 64-битной системе это 64 бита var big = 2**62 print("большое целое:", big)
# Размерные типы указываются явно и НЕ смешиваются с Int автоматически var small: Int8 = 127 small += 1 print("Int8 после переполнения:", small)
# Деление: осторожно var a = 7 var b = 2 print("Int / Int:", a / b) print("после преобразования:", Float64(a) / Float64(b)) print("целочисленное деление:", a // b)
# SIMD — обычный тип языка, операции идут сразу над всеми элементами var vector = SIMD[DType.float32, 4](1, 2, 3, 4) print("вектор * 2:", vector * 2) print("сумма элементов:", vector.reduce_add())
# Преобразования всегда явные var text = String(255) var back = Int(text) print("строка и обратно:", text, back + 1)большое целое: 4611686018427387904 Int8 после переполнения: -128 Int / Int: 3 после преобразования: 3.5 целочисленное деление: 3 вектор * 2: [2.0, 4.0, 6.0, 8.0] сумма элементов: 10.0 строка и обратно: 255 256
Коллекции
Заголовок раздела «Коллекции»var numbers: List[Int] = [1, 2, 3]var ages = Dict[String, Int]()var buffer = InlineArray[Int, 3](fill=0)Все они обобщённые (generic): тип элементов указывается в квадратных
скобках и известен компилятору. Список List[Int] хранит числа подряд
в памяти — без обёрток, заголовков и указателей, как это устроено
в Python. Отсюда и скорость, и требование однородности.
InlineArray отличается от List тем, что его длина известна на этапе
компиляции, и он не выделяет память в куче — живёт прямо на стеке.
Для маленьких буферов фиксированного размера это заметно быстрее.
Стандартные типы не привилегированы
Заголовок раздела «Стандартные типы не привилегированы»Важная особенность языка: Int, String, List не встроены в компилятор
на особых правах. Они написаны на самом Mojo, в стандартной библиотеке,
теми же средствами, что доступны вам. Свой числовой тип с перегруженными
операторами будет ровно таким же гражданином языка, как Int.
Это не философия, а практическое следствие: язык можно расширять, не дожидаясь новых версий компилятора.
🎯 Проверь себя
Почему `7 / 2` даёт 3.5, а `a / b` для двух Int — 3?
В первом случае делятся литералы: компилятор вычисляет выражение
на этапе сборки и получает дробное число. Во втором делятся значения
типа Int, а деление Int на Int даёт Int с отброшенной дробной
частью. Нужна дробь — приводите к Float64 явно.
Чем `Int` отличается от `Int64`?
Int — целое размером в машинное слово (на 64-битных системах это
64 бита), Int64 — ровно 64 бита независимо от платформы. Это разные
типы, и неявного преобразования между ними нет. Int — для обычной
арифметики и индексов, размерные типы — когда важен точный формат данных.
Что напечатает `var x: Int8 = 127` с последующим `x += 1`?
-128. Однобайтовый тип переполняется без всякой ошибки: 127 — его
максимум, дальше значение заворачивается. В Python целые растут
неограниченно, в Mojo — нет.
Что дальше
Заголовок раздела «Что дальше»Строки заслуживают отдельного разговора — особенно когда в них кириллица: Строки и Unicode.
Тексты курса — CC BY-NC-SA 4.0, код примеров — Apache 2.0