Конвенции передачи аргументов
This content is not available in your language yet.
В Python у аргумента нет никакого «режима»: он просто передаётся, а что с ним можно делать — выясняется опытным путём.
В Mojo режим написан в сигнатуре. Их пять, но в повседневном коде вы будете пользоваться первыми тремя.
| Слово | Что функция получает | Когда нужно |
|---|---|---|
ничего (или imm) | ссылку только для чтения | по умолчанию, почти всегда |
mut | изменяемую ссылку | функция меняет аргумент на месте |
var | владение значением | функция забирает значение себе |
ref | ссылку, происхождение которой можно назвать | функция возвращает ссылку наружу |
out | место под результат | конструкторы и фабрики |
По умолчанию — только чтение
Заголовок раздела «По умолчанию — только чтение»def describe(value: Int): print(value)Копии не происходит: функция получает ссылку. Но менять по ней нельзя.
error: expression must be mutable for in-place operator destination
Вы попытались изменить аргумент, переданный по умолчанию — а он доступен только для чтения.
Решите, что вам нужно:
- менять значение у вызывающего кода — пометьте аргумент
mut; - менять локально, не трогая оригинал — возьмите владение (
var) или сделайте копию внутри функции.
mut — изменение на месте
Заголовок раздела «mut — изменение на месте»def add_page(mut pages: Int): pages += 1
def main(): var pages = 10 add_page(pages) add_page(pages) print(pages)12
При вызове ничего дописывать не нужно — add_page(pages), как в Python.
Но из сигнатуры сразу видно, что функция меняет то, что ей дали.
def add_page(report): report["pages"] += 1 # меняет? не меняет?def add_page(mut report: Report): report.pages += 1 # видно из сигнатурыВ Python ответ зависит от типа: список и словарь функция изменит,
число или строку — нет. В Mojo про это не гадают: mut в сигнатуре
или его отсутствие отвечают на вопрос сразу.
Напомним ограничение из главы Функции: у изменяемого
аргумента не может быть значения по умолчанию (error: 'mut' arguments must not have defaults). Логика простая — менять нечего, если значение
не передали.
var — передача владения
Заголовок раздела «var — передача владения»Функция забирает значение себе. Вызывающий код обязан это подтвердить
сигилом ^:
def archive(var title: String): title += " (в архиве)" print(title)
def main(): var title = String("Отчёт") archive(title^)Отчёт (в архиве)
После вызова переменная title недоступна: владение ушло. Значение
уничтожится внутри archive, когда перестанет быть нужным там.
ref — чтобы вернуть ссылку
Заголовок раздела «ref — чтобы вернуть ссылку»Самая непривычная конвенция. ref нужен не для того, чтобы что-то менять,
а для того, чтобы функция могла вернуть ссылку на один из своих
аргументов:
def longer(ref a: String, ref b: String) -> ref [a, b] String: return a if a.byte_length() > b.byte_length() else b
def main(): var short = String("раз") var long = String("двадцать") print(longer(short, long))двадцать
В квадратных скобках после ref перечислено, откуда ссылка может
происходить: из a или из b. Компилятору это нужно, чтобы гарантировать,
что возвращённая ссылка не переживёт то, на что указывает.
Результат — настоящая ссылка, в неё можно писать:
def longer(ref a: String, ref b: String) -> ref [a, b] String: return a if a.byte_length() > b.byte_length() else b
def main(): var short = String("раз") var long = String("двадцать") longer(short, long) = String("заменено") print(short, "|", long)раз | заменено
Кстати, mut-аргумент тоже годится как источник для возвращаемой ссылки —
но не для всех типов:
error: cannot return 'a's origin, because it has RegisterPassable type 'Int'
Маленькие типы вроде Int передаются в регистрах процессора,
то есть mut a: Int — это копия. Вернуть на неё ссылку означало бы
отдать наружу ссылку на копию, которой скоро не станет.
Возьмите ref вместо mut: def pick(ref a: Int) -> ref [a] Int
компилируется, и писать через результат тоже можно. Ограничение
касается именно mut, а не чисел вообще.
С обычным аргументом не выйдет ни так, ни так — у него просто нет происхождения в памяти:
error: value of type 'Int' doesn't have a memory origin in origin specifierout — результат через аргумент
Заголовок раздела «out — результат через аргумент»Вы уже видели out в конструкторах: def __init__(out self, ...).
Работает он и в обычных функциях — как именованное место под результат:
def make_default(out title: String): title = String("Без названия")
def main(): var title = make_default() print(title)Без названия
Вызов выглядит как обычный: var title = make_default(). Разница в том,
что значение строится сразу в переменной вызывающего, без промежуточного
результата.
Рядом с out можно объявлять обычные аргументы, в том числе со значениями
по умолчанию. А вот два ограничения компилятор проверяет строго:
error: function may not have multiple 'out' arguments
Больше одного out в функции быть не может: результат у функции один.
Нужно вернуть несколько значений — возвращайте кортеж, как обычно.
error: functions must not declare both an 'out' argument and a return type
out-аргумент и стрелка -> — два способа сказать одно и то же.
Вместе они не имеют смысла.
Оставьте что-то одно. В обычном коде проще стрелка; out нужен там,
где результат обязан строиться прямо на месте — прежде всего
в конструкторах.
Всё вместе
Заголовок раздела «Всё вместе»# Пять конвенций передачи аргументов на одном примере.
@fieldwise_initstruct Report(ImplicitlyCopyable, Writable): """Отчёт: название и число страниц."""
var title: String var pages: Int
def write_to[W: Writer](self, mut writer: W): """Печатает отчёт человекочитаемо.""" writer.write(self.title, ", страниц: ", self.pages)
def describe(report: Report): """Только читает аргумент — конвенция по умолчанию.""" print(" ", report)
def add_page(mut report: Report): """Меняет аргумент на месте, изменение видно вызывающему коду.""" report.pages += 1
def archive(var report: Report): """Забирает владение: после вызова оригинал недоступен.""" report.title += " (в архиве)" print(" ", report)
def longer(ref a: String, ref b: String) -> ref [a, b] String: """Возвращает ссылку на более длинную из двух строк.""" return a if a.byte_length() > b.byte_length() else b
def make_default(out report: Report): """Создаёт значение прямо в аргументе-результате.""" report = Report("Без названия", 1)
def main(): var report = Report("Отчёт за август", 10)
print("по умолчанию — только чтение:") describe(report)
print("mut — изменение на месте:") add_page(report) add_page(report) describe(report)
print("ref — ссылка, которую можно вернуть наружу:") var short = String("раз") var long = String("двадцать") print(" длиннее:", longer(short, long)) longer(short, long) = String("заменено") print(" после записи по ссылке:", short, "|", long)
print("out — результат приходит через аргумент:") var fresh = make_default() describe(fresh)
print("var — передача владения:") archive(report^)по умолчанию — только чтение: Отчёт за август, страниц: 10 mut — изменение на месте: Отчёт за август, страниц: 12 ref — ссылка, которую можно вернуть наружу: длиннее: двадцать после записи по ссылке: раз | заменено out — результат приходит через аргумент: Без названия, страниц: 1 var — передача владения: Отчёт за август (в архиве), страниц: 12
Как выбирать
Заголовок раздела «Как выбирать»Порядок принятия решения на каждый день:
- Функция только читает аргумент? Не пишите ничего — это уже правильно и не стоит ни копейки.
- Функция меняет аргумент, и изменение должно быть видно снаружи?
mut. - Функция забирает значение навсегда — кладёт в контейнер, пишет,
отправляет?
var, а на стороне вызова^. - Функция возвращает ссылку на свой аргумент?
refи происхождение в скобках. - Вне конструкторов
outнужен редко — разве что в фабриках вродеmake_defaultиз примера выше.
🎯 Проверь себя
Чем `ref` отличается от `mut`?
mut даёт изменяемую ссылку: аргумент можно менять прямо в теле
функции. ref нужен для другого — чтобы функция могла вернуть ссылку
на свой аргумент наружу; внутри функции такой аргумент доступен только
для чтения.
Что нужно написать на стороне вызова для аргумента `var`?
Сигил ^: archive(title^). Так в коде видно, что значение уходит
навсегда. После этого переменная недоступна.
Что означают квадратные скобки в `-> ref [a, b] String`?
Происхождение ссылки: она может указывать на a или на b.
Компилятору это нужно, чтобы гарантировать, что ссылка не переживёт
значение, на которое ссылается.
Какую конвенцию выбрать, если функция просто читает аргумент?
Никакую: поведение по умолчанию и есть ссылка только для чтения.
Копии при этом не происходит. Явное имя у этой конвенции — imm,
писать его не обязательно.
Что дальше
Заголовок раздела «Что дальше»Копирование и перемещение: что именно делает
сигил ^, чем move-only типы отличаются от копируемых и сколько стоит
каждая операция.
Тексты курса — CC BY-NC-SA 4.0, код примеров — Apache 2.0