Поток управления

В Ü существует ряд конструкций по управлению потоком управления.

Условия

Оператор if позволяет выполнить какой-либо участок кода только при выполнение какого-то условия. Условие в операторе if должно иметь тип bool.

if( x > y )
{
    ++x;
}

Можно указать действие, которое будет выполнено в случае, если условие не выполнилось:

if( x > y )
{
    ++x;
}
else
{
    ++y;
}

Можно указать несколько условий и действий при их выполнении:

if( x > y )
{
    ++x;
}
else if( y > x )
{
    ++y;
}
else if( z )
{
    return;
}
else
{
    ++x;
    ++y;
}

Статические условия

Существует особый вид условного оператора - static_if. Он принимает только constexpr выражения в качестве условий. Соотвественно, какая ветвь условия будет выполнятся - определяется статически, при компиляции. Ветви, которые не должны исполняться, компилироваться не вообще будут.

static_if( typeinfo</size_type/>.size_of == 8s )
{
    return 0; // Скомпилируется или эта ветвь
}
else static_if( typeinfo</size_type/>.size_of == 4s )
{
    return 1; // Или эта
}
else
{
    // В экзотических случаях может скомпилироваться и эта ветвь
    static_assert(false); // но компиляция упадёт на этом ассерте
    halt;
}

Комбинирование условий

Условные операторы различных видов - if, static_if а также if_coro_advance можно комбинировать друг с другом любым образом. После else любого из этих операторов может следовать любой другой из этих операторов. Фактически добавление любого из условных операторов после else аналогично добавлению блока после else, содержащего внутри этот оператор.

К примеру, такой код:

static_if( static_condition )
{
    Action0();
}
else if( dynamic_condition )
{
    Action1();
}
else if_coro_advance( x : some_gen )
{
    Action2(x);
}
else
{
    Action3(x);
}

Эквивалентен такому коду:

static_if( static_condition )
{
    Action0();
}
else
{
    if( dynamic_condition )
    {
        Action1()
    }
    else
    {
        if_coro_advance( x : some_gen )
        {
            Action2(x);
        }
        else
        {
            Action3();
        }
    }
}

Оператор switch

В Ü существует оператор switch, который позволяет передать поток управления определённому блоку, в зависимости от значения некоторой переменной.

Как выглядит простейший switch:

switch(x)
{
    0 -> { return -1; },
    1 -> {},
    2 -> { halt; },
    // другие обработчики ниже
}

Работает этот оператор только для целочисленных и символьных типов а также типов перечислений. Значения, с которыми сравнивается исходная переменная, должны быть constexpr.

Оператор switch позволяет указывать несколько значений для одного блока кода - через запятую.

Также возможно указание диапазонов значений. Управление передастся блоку кода, если значение больше либо равно минимальной границе диапазона и меньше либо равно максимальной границе. Минимальную и максимальную границы можно опускать, тогда минимальной границей считается минимально-возможное значение типа, а максимальной границей - максимально-возможное.

Для значений, не перечисленных явно (всех остальных) можно указать default обработчик. Он не обязательно должен быть указан последним, главное чтобы его было не более одного.

switch( x )
{
    33, ... -7, 66 ... 78, 999 ... -> { return 777; }, // значение 33, диапазон [-int_max; -7], диапазон [66; 78], диапазон [999; int_max]
    96 ... 108, 80 -> { return 888; }, // диапазон [96; 108], значение 80
    82, 200 ... 300 -> { return 999; }, // значение 82, диапазон [200; 300]
    default -> { return 1000; }, // всё, что не попадает в вышеперечисленное
}

Компилятор проверяет, чтобы оператор switch покрывал все возможные значения типа. Если это не так - будет порождена ошибка.

enum E{ A, B, C }

fn Foo( E e )
{
    switch( e )
    {
         E::A -> {},
         E::B -> {},
         // ошибка - E::C не обработан
    }
}

При этом если присутствует default обработчик, то считается что он обработает все значения, которые не указаны. Но если оператор default излишен, компилятор породит ошибку.

fn Foo( u8 x )
{
    switch( x )
    {
         0u8 -> {},
         1u8 -> {},
         2u8 ... -> {}, // Покрывает все значения от 2 до максимума
         default -> {}, // ошибка - этот обработчик недостижим
    }
}

Цикл while

Оператор while позволяет повторять какие-либо действия, пока условие истинно. Условие в операторе while должно иметь тип bool.

while( x > 0 )
{
    --x;
}

Можно выйти из цикла преждевременно, используя оператор break:

while( x > 0 )
{
    x /= 5;
    if( x == 1 )
    {
        break;
    }
}

Можно перейти к следующей итерации цикла, используя оператор continue:

while( x > 0 )
{
    x /= 3;
    if( x == 5 )
    {
        continue;
    }
    --x;
}

Цикл while (а также циклы других типов) можно пометить меткой (с использованием ключевого слова label) и использовать эту метку в break и continue. Таким образом можно реализовать продолжение внешнего цикла или выход из внешнего цикла.

while( Cond0() ) label outer
{
    while( Cond1() )
    {
        if( Cond2() )
        {
            continue label outer; // продолжить внешний цикл
        }
        if( Cond3() )
        {
            break label outer; // завершить внешний цикл
        }
    }
}

Цикл for

Также в Ü есть цикл for, похожий на таковой в C++. Состоит он из трёх частей - части объявления переменных, части условия и части операций конца итерации. Части разделяются при помощи ;. Каждая часть является опциональной, если не указана часть условия, цикл будет выполняться бесконечно или завершится при помощи break. Цикл for позволяет выполнить какое-либо действие в конце итерации всегда, каждый оператор continue будет осуществлять переход на действие в конце итерации.

Примеры цикла for:

auto mut x= 0;
for( auto mut i= 0; i < 10; ++i )  // Объявление переменных через auto
{
   x+= i * i;
}
auto mut x= 0;
for( var i32 mut i= 0, mut j= 2; i < 5; ++i, j*= 2 ) // Объявление переменных через var, более одного действия в конце итерации
{
   x+= i * j;
}
for( auto mut i = 1; ; i <<= 1u ) // Цикл с пустым условием
{
   if( i < 0 ){ break; }
}
for( ; ; ) // Цикл совсем без всего
{
   break;
}
for( var u32 mut x= 0u; x < 100u; ++x )
{
   if( SomeFunc(x) ){ continue; } // После continue будет вызван код ++x
   SomeFunc2(x);
}

Цикл loop

В Ü есть цикл loop, который является самым простым (безусловным) циклом. Он не содержит даже условия и в этом плане отчасти эквивалентен циклу while с условием, всегда равным true. Завершить такой цикл можно только при помощи операторов break и return, ну или continue к метке внешнего цикла. Имеет смысл использовать этот цикл, когда условие завершение цикла вычислимо только в блоке самого цикла.

loop
{
   // Some code
   if( SomeCondition() )
   {
      break;
   }
}

Важной особенностью цикла loop является тот факт, что если тело цикла не содержит break операторов (относящихся к этому циклу), код после этого цикла считается недостижимым.

loop
{
   if( SomeCondition() )
   {
      return;
   }
}
auto x = 0; // Компилятор породит здесь ошибку, т. к. этот код недостижим.

Возврат из функции

Исполнение функции, не возвращающей значение, заканчивается, когда поток исполнения достигает конца тела функции. Если зачем-то нужно завершить исполнение функции раньше, можно использовать оператор return.

fn Clamp( i32 &mut x )
{
    if( x >= 0 )
    {
        return;
    }
    x= 0;
}

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

fn Add( i32 x, i32 y ) : i32
{
    return x + y;
}

Компилятор проверяет, во всех ли случаях функция возвращает значение, и, если это не так, будет порождена ошибка.

fn Clamp( i32 &mut x ) : bool
{
    if( x >= 0 )
    {
        return false;
    }
    x= 0;
    // Ошибка, функция возвращает значение не во всех случаях.
}
fn Clamp( i32 &mut x ) : bool
{
    if( x >= 0 )
    {
        return false;
    }
    else
    {
        x= 0;
        return true;
    }
    // Всё в порядке, функция возвращает значение всегда
}