Контроль ссылок

Контроль ссылок - одна из ключевых особенностей Ü, позволяющих обеспечить отсутствие ошибок в программах. Данный механизм позволяет на этапе компиляции обнаружить ошибки обращения к освобождённой памяти, ошибки алиазинга, ошибки с висящими ссылками и т. д.

Правила контроля ссылок

Главное правило контроля ссылок звучит так: в каждой точке потока управления переменная или ссылка должны иметь или от нуля до бесконечности производных неизменяемых ссылок, или только одну производную изменяемую ссылку. Компилятор в процессе компиляции проверяет соблюдение этого правила и при его нарушении порождает ошибку.

Производные ссылки

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

fn Foo()
{
    var i32 x = 0;
    var i32 &y= x; // "у" - производная ссылка от "x"
    var i32 &z= y; // "z" - производная ссылка от "y"
}

Ссылка на член массива считается производной ссылкой от ссылки/переменной массива.

fn Foo()
{
    var [ f64, 4 ] a= zero_init;
    var f64 &a_ref= a[2]; // "a_ref" - производная ссылка от "a"
}

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

fn Pass( f32 &mut x ) : f32 &mut
{
    return x;
}

fn Min( i32 & a, i32 & b ) : i32 &
{
    if( a < b ) { return a; }
    return b;
}

fn Foo()
{
    var f32 mut f= 0.5f;
    var f32 &mut f_ref= Pass(f); // "f_ref" - производная ссылка от "f"

    var i32 a= 8, b= 7;
    var i32 &ab_ref= Min(a, b); // "ab_ref" - производная ссылка сразу от двух переменных - "a" и "b"
}

Ссылка внутри структуры тоже может считаться производной.

struct S{ i32& r; }
fn Foo()
{
    var i32 x= 0;
    var S s{ .r= x }; // "s.r" - производная ссылка от "x"
    var i32& r2= s.r; // "r2" - производная ссылка от ссылки "s.r"
}

Дочерние ссылки

Концепт дочерних ссылок несколько отличается от производных ссылок. Дочерние ссылки - это ссылки на нессылочные поля структур и классов и элементы кортежей. Главное отличие дочерних ссылок от производных заключается в том, что можно иметь более одной изменяемой ссылки на одну переменную, если это дочерние ссылки на разные её члены (поля или элементы для кортежей). Это позволяет, например, одновременно менять разные поля одного и того же экземпляра структуры.

struct S{ i32 x; i32 y; }
fn Swap( i32 &mut a, i32 &mut b );
fn Foo()
{
    var S mut s= zero_init;
    var tup[i32, i32] mut t= zero_init;
    var i32 &mut x_ref= s.x; // Создана первая дочерняя изменяемая ссылка на поле "x" структуры.
    var i32 &mut y_ref= s.y; // Ok - создана вторая изменяемая тот же экземпляр структуры, но на другое поле "y".
    Swap( t[0], t[1] ); // Одновременно изменяем разные элементы одного и того же экземпляра кортежа.
}

Управление производными ссылками в функциях

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

Каждый ссылочный аргумент функции можно пометить специальным тегом, который можно использовать для пометки возвращаемой ссылки. В этом случае будет считаться, что функция возвращает производную ссылку только от тех аргументов, чей тег совпадает с тегом возвращаемой ссылки. Тег указывается как идентификатор с ' в начале после модификатора ссылочности &.

fn Foo( i32 &'tag0 a, i32 &'tag1 b ) : i32 &'tag0; // Данная функция возвращает производную ссылку только от аргумента "a"
fn Bar( f32 &'tag_r a, f32 &'tag1 b, f32 &'tag_r c ) : f32 &'tag_r; // Данная функция возвращает производную ссылку только от аргументов "a" и "c"

fn Baz()
{
    var i32 i0= 0, i1= 0;
    var f32 f0= 0.0f, f1= 0.0f, f2= 0.0f;
    var i32 &i_ref= Foo(i0, i1); // "i_ref" - производная ссылка от переменной "i0", но не от "i1"
    var f32 &f_ref= Bar(f0, f1, f2); // "f_ref" - производная ссылка от "f0" и "f2", но не от "f1"
}

Если функция помечена, как возвращающая ссылку, производную только от части своих ссылочных аргументов, компилятор проконтролирует, что в теле функции не возвращается производная ссылка от запрещённого аргумента.

fn Foo( i32 &'tag0 a, i32 &'tag1 b ) : i32 &'tag0
{
   return b; // Будет порождена ошибка - возвращение недозволенной ссылки
}

Также можно пометить ссылки, встречающиеся внутри переменных. Для этого надо использовать тег в '' кавычках после имени аргумента (для аргументов) или после имени типа (для возвращаемых значений).

struct S{ i32& r; }

fn Foo( i32 &'tag0 a, S s'tag0', i32 &'tag1 z ) : S'tag0'
{
    if( a > s.r && z != 0 )
    {
        var S ret{ .r= a };
        return ret;
    }
    else
    {
        var S ret{ .r= s.r };
        return ret;
    }
}

Связывание ссылок

Некоторые функции могут создавать производные ссылки от своих аргументов внутри других аргументов. Это называется связыванием ссылок. Для функции, осуществляющей связывание ссылок, надо указать список связывания, сразу после списка аргументов. Список состоит из пар назначение/источник, связанных через <- и перечисленных через запятую внутри ''.

struct S{ i32& r; }
fn Foo( S &mut s'dst', i32 &'src r ) ' dst <- src '; // Функция создаёт производную ссылку от аргумента "r" внутри аргумента "s".

fn Bar()
{
    var i32 x= 0, y= 0;
    var S mut s{ .r= x }; // "s.r" является производной ссылкой от "x"
    Foo( s, y ); // Теперь "s.r" является производной ссылкой ещё и от "y"
}

Если функция фактически осуществляет связывание ссылок для своих аргументов, но в её заголовке это не указано - компилятор породит ошибку.

struct S{ i32& r; }
fn Foo( S &mut s'dst', i32 &'src r ) ' dst <- src '; // Функция создаёт производную ссылку от аргумента "r" внутри аргумента "s".

fn Bar( S &mut s, i32 & r )
{
    Foo(s, r); // // Будет порождена ошибка - недозволенное связывание ссылок
}

Обнаружение нарушения правила контроля ссылок

В примерах ниже отражено, как правило контроля ссылок осуществляется на практике.

fn Foo()
{
    var i32 mut x= 0;
    var i32 &mut r0= x; // "r0" - изменяемая производная ссылка от "x"
    var i32 &imut r1= x; // Создание производной от "x" ссылки, когда уже существует производная изменяемая ссылка. Будет порождена ошибка.
}
fn Foo()
{
    var f32 mut x= 0.0f;
    var f32 &imut r0= x; // "r0" - неизменяемая производная ссылка от "x"
    var f32 &mut r1= x; // Создание изменяемой производной от "x" ссылки, когда уже существует производная ссылка. Будет порождена ошибка.
}
fn MutateArgs( f64 &mut a, f64 &mut b );

fn Foo()
{
    var f64 mut x= 0.0;
    MutateArgs( x, x ); // Будет порождена ошибка. Для вызова функции одновременно создаются две производные от переменной "x" ссылки.
}

Обнаружение нарушения времени жизни

Контроль ссылок также позволяет отловить ошибки висящих ссылок, когда время жизни переменной истекло.

struct S{ i32& r; }
fn Foo( S &mut s'dst', i32 &'src r ) ' dst <- src '; // Функция создаёт производную ссылку от аргумента "r" внутри аргумента "s".

fn Bar()
{
    var i32 x= 0;
    var S mut s{ .r= x };
    {
        var i32 y= 0;
        Foo( s, y );
    } // Будет порождена ошибка - переменная "y" всё ещё имеет ссылки на себя при разрушении.
}

Контроль ссылок не позволяет возвращать ссылки на локальные переменные.

fn Foo( i32& arg ) : i32 &
{
    var i32 x= 0;
    return x; // Будет порождена ошибка - переменная "x" всё ищё имеет ссылки на себя при разрушении.
}