Разделы сайта находятся в разработке.

Материалы могут быть неполными или содержать ошибки. Содержимое сайта будет синхронизировано с реализацией при выходе следующей версии, тогда как текущая (0.6.0) предназначена для прототипирования синтаксиса и создания песочницы.

Функции

Функция определяется операторами создания объектов, имя функции должно соответствовать правилам именования и завершаться круглыми скобками. Телом обычной функции служит блок кода, находящийся в пространстве имён её определения.

У аргументов и у типа возвращаемого значения указывается тип (arg:Type).

Наборы допустимых типов

Несколько допустимых типов задаются набором типов — математическими операторами + (объединение) и - (исключение). Набор определяется один раз как именованный тип:

combi ::= :Int8 + :Int16 + :Int32;   # именованный набор

Функция, у которой параметр имеет тип-набор, разворачивается в N определений (по одному на член набора) с общим телом; вызов разрешается по типу аргумента, как при явной перегрузке:

sq(x:combi):Int64 := { @return :Int64(x) * :Int64(x); };
# эквивалентно трём определениям: sq(:Int8), sq(:Int16), sq(:Int32)

sq(:Int16(3));    # выберет определение с параметром :Int16

Набор можно указать и inline — непосредственно в позиции типа (сигл : идёт сразу перед ():

sq2(x:(:Int8 + :Int16)):Int64 := { @return :Int64(x) * :Int64(x); };

Один и тот же именованный набор может стоять и в параметре, и в возврате — тогда определения сопоставляются позиционно (i-й член набора — и для параметра, и для возврата):

combi ::= :Int8 + :Int16;
idk(x:combi):combi := { @return x; };   # 2 определения: (:Int8)->:Int8, (:Int16)->:Int16

Правила и ограничения:

  • В прототипе допустим не более одного различного набора (иначе — ошибка о комбинаторной сложности); один и тот же набор может использоваться в нескольких позициях.
  • Набор только в возврате недопустим: перегрузки различались бы лишь типом возврата (в C++ перегрузки по возврату нет). Используйте тот же набор и в параметре.
  • Inline-набор — только тип, не выражение (нельзя присвоить/передать как значение).
  • Исключение - пока не реализовано (явная ошибка).
  • Членами набора могут быть простые типы (шаблонные типы в наборе пока не поддерживаются).

TrustLang на уровне синтаксиса поддерживает несколько видов функций.

Обычные функции

Обычная функция ничем не ограничена в смысле C/C++: может содержать условия, циклы, вызовы других функций и т. д. Создаётся оператором :=:

hello(str:StrChar) := {
    @print('call: {}\n', str);
};

@main() := {
    hello('Привет, мир!');
};

Именованные и опциональные параметры

Именованные параметры позволяют передавать аргументы по имени (с префиксом «.»), а не по позиции:

func(width:Int32, height:Int32) := { ... };

func(.width=640, .height=480);
func(.height=480, .width=640);  # порядок не важен

Имя именованного аргумента не перекрывается макросом препроцессора (см. Именование объектов).

Опциональные параметры имеют значение по умолчанию и могут быть опущены при вызове: имя аргумента отделяется от значения знаком =. Сначала перечисляются обязательные параметры, затем — со значениями по умолчанию. Функция, допускающая произвольное число аргументов, заканчивает список параметров многоточием ....

Чистые функции

Чистая функция не имеет побочных эффектов: возвращает результат только по своим аргументам и не изменяет состояние программы. Синтаксис декларации ::- не реализован (см. Статус); к понятию чистоты относится и требование иммутабельности аргументов по ссылке (см. Именование объектов).

Сопрограммы (coroutines)

Сопрограмма — функция-замыкание, способная приостанавливать и возобновлять выполнение (кооперативная многозадачность). В текущей версии не реализовано — см. Статус и раздел Многопоточность и асинхронность.

Анонимные функции

Анонимная функция объявляется в месте использования и не получает собственного имени (уникального идентификатора). В TrustLang анонимная функция задаётся лямбда-выражением и представляет собой значение функционального типа std::function<...>: его можно сохранить в переменной, передать аргументом (функция обратного вызова, предикат) или вызвать на месте.

Лямбда-выражения (замыкания)

Лямбда-выражение объявляет анонимную функцию по месту применения:

[ captures ] (params) : Ret { body }
  • captures — список захватов: только имена ранее объявленных переменных, по значению (копия). Пустые скобки [] — без захвата. Ссылочный захват [&x] в текущей версии не поддерживается (диагностика об ошибке).
  • params — параметры как у обычной функции (x:Type, тип опционален).
  • : Ret — тип возврата; при отсутствии выводится из тела.
  • body — блок кода { ... }; значение блока — результат лямбды.

Лямбда-значение типизируется функциональным типом и может храниться/передаваться. Возврат значения — как у обычной функции (@return / именованная форма), лямбда внутренне имеет собственную метку возврата:

f := [](x:Int32):Int32 { @return x * x; };
y := f(5);

Немедленный вызов

Лямбду можно вызвать сразу после определения. Каноническая форма — обёрнутая:

y := ( [](x:Int32):Int32 { @return x * x; } )(5);

Прямая форма [](x){... }(5) (без обёртки) не поддерживается и выдаёт ошибку с подсказкой использовать ( lambda )(args).

Не поддерживается (первая итерация)

Ссылочный захват [&x], неявный захват [=]/[&], захват со значением/типом ([x = expr], [x:Type]) и захват некопируемых значений (эксклюзивное владение unique) — не поддерживаются и дают явную диагностику. «Замыкание по определению не может быть чистой функцией» — чистая функция (::-) в языке пока не реализована.

Переопределение и перегрузка функций

Поскольку TrustLang позволяет вызывать нативные C/C++-функции напрямую и делать вставки C++-кода, переопределение и перегрузка функций имеют свои особенности.

  • Переопределение (overriding) — реализация методом подкласса метода, объявленного в суперклассе (фундаментальный принцип ООП).
  • Перегрузка (overloading) — создание нескольких функций с одним именем, но разным набором параметров; нужный вариант выбирается на этапе компиляции по параметрам.

Переопределение (overriding)

Переопределение допускается не только для методов класса, но и для обычных функций (механизм, похожий на LD_PRELOAD, реализованный средствами самого языка). Чтобы функцию можно было переопределить, при её создании не должен быть указан признак иммутабельности. Если суперкласс объявил метод мутабельным (виртуальным), а подкласс реализует его с признаком иммутабельности, это равносильно override final (без дальнейшего переопределения):

base::func() := { ... };   # virtual
class1::func() = { ... };  # override
class2::func^() = { ... }; # override final

Переопределение идёт по имени функции без учёта аргументов, однако новая функция должна иметь аргументы, совместимые с типом аргументов исходной функции.

Перегрузка (overloading)

Перегрузка — несколько функций с одним именем и разными сигнатурами (набором типов параметров); нужный вариант выбирается статически на этапе компиляции по типам аргументов. Точный дубль сигнатуры — ошибка; если подходящего варианта нет — ошибка no matching overload, если несколько вариантов несравнимы — ambiguous.

Перегрузка свободных функций и методов пользовательских классов/Struct поддержана:

f(a:Int32):Int32 := { @return a + 1; };
f(a:Int64):Int32 := { @return 100; };

@main() := {
    x : Int32 := 5;
    @print('{}\n', f(x));        # выбран f(a:Int32)
    @print('{}\n', f(7:Int64));  # выбран f(a:Int64)
};

:C ::= :Class{
    base:Int32 := 10;
    f(a:Int32):Int32 := { @return a + 1; };   # перегрузка МЕТОДА
    f(a:Int64):Int32 := { @return base + 90; };
};

Выбор варианта — по типам аргументов: точное совпадение предпочтительнее числового расширения (промоция/конверсия), параметр :Any принимает любой тип (низший приоритет); при нескольких несравнимых лучших — ambiguous.

Перегрузку (выбор сигнатуры и допустимость/приведение аргументов) для функций и методов TrustLang всегда выполняет компилятор — и при единственной сигнатуре тоже. Делегирование C++-слою применяется только к нативным функциям/методам (их типы заданы явно нативными, а имя символа фиксировано внешней библиотекой).

Перегрузка по-прежнему возможна и для нативных функций (все типы аргументов указываются явно и являются нативными типами); нативные функции также доступны для прямого вызова из языков C/C++:

%func(arg:Int8):None := {};   # void func(int8_t)
%func(arg:Byte):None := {};   # void func(uint8_t)
%func(arg:Int64):None := {};  # void func(int64_t)