Поток управления¶
В Ü существует ряд конструкций по управлению потоком управления.
Условия¶
Оператор 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;
}
// Всё в порядке, функция возвращает значение всегда
}