воскресенье, 22 декабря 2019 г.

Владение ресурсами в Rust. Часть 3.

Двигаемся дальше. Сегодня мы рассмотрим передачу по ссылке (без передачи владения). Про передачу по значению (с передачей владения) мы уже все знаем и останавливаться на этом не будем. У нас есть возможность избежать и копирование, и перемещение с помощью прямой ссылки на область памяти, которая хранит наше значение, т. е. на наш ресурс. Кстати при перемещении происходит нечто подобное по смыслу, мы просто передаем (перемещаем) адрес памяти (с нашим значением) от одной переменной к другой. Исходным условием этого является наличие именованной области памяти, созданной явно (с помощью переменной) или не явно (литерал значения). Таким образом мы можем ссылаться на уже существующую переменную или значение, но наиболее полезной является возможность принимать и возвращать ссылки из функции. Более сложные способы применения, а именно наличие ссылок в виде полей пользовательских типов, мы рассмотрим следующий раз. Тип ссылка определяется с помощью символа &, например: let x:i32 = 5; let r_x:&i32 = &x;. Указывать тип не обязательно, он выводится автоматически: let r_x = &x;

Как мы помним, в си++ ссылки появились как инструмент упрощающий работу с указателями. По сути это синтаксический сахар и внутри мы имеем указатели, а значит ссылки явно или не явно имеют значение типа в своем определении. Ссылки очень полезны в работе, т. к. мы не копируем сами области памяти с данными, а копируем только значения адресов памяти, что значительно меньше нагружает систему и ускоряет работу приложения. Например, при передаче больших данных в функцию и из нее, мы можем избежать дублирования этих данных. В отличие от ссылок в C++, где ссылки по умолчанию позволяют модифицировать данные, в Rust по умолчанию эти данные не доступны для модификации. Чтобы сделать возможным изменение данных потребуется использовать mut в определении ссылки. Например, let r_xx = &mut xx;. Чтобы изменить значение, на которое указывает ссылка, нам нужно разыменовать ссылку с помощью оператора *, например: *r_xx = 10;.

Обращаю ваше внимание на то, что обозначение ссылки (включая возможность модификации) производится явно и для параметра, и для аргумента функции. Это отличает Rust от C++ в лучшую сторону, т.к. при передаче аргумента в си++ (без указания, что мы передаем по ссылке), затрудняется чтение и понимание кода и требуется знания параметров функции, благо современные IDE облегчают эту задачу. По умолчанию ссылка может быть проинициализирована один раз и мы не можем ее изменить до тех пор, пока не укажем еще один mut в определении:  let mut r_xx = &mut xx;. Теперь мы можем присвоить ей новое значение, например: r_xx = &mut yy;. Но есть еще одно отличие от C++ в котором, мы можем иметь несколько модифицирующих ссылок на один объект, что в свою очередь снижает безопасность. В Rust это невозможно, в определенной области для одного ресурса мы можем иметь только одну модифицирующую ссылку ИЛИ несколько не модифицирующих.  Это ограничение предотвращает гонку за данными. При необходимости мы можем разделить ссылки в разных областях действия или блоках.

При передаче по ссылке не происходит передачи владения ресурсом, при окончании времени жизни ссылки не происходит уничтожения значения и освобождения памяти. Область действия ссылки начинается с того места где она была введена и продолжается до того места где она была последний раз использована. Если области действия ссылок не пересекаются, то гонки за данными не происходит. При возврате ссылки на локальную переменную из функции, в отличие от C++ в Rust потребуется указать специальный атрибут времени жизни, что предотвратит создание опасных висячих ссылок, об этом мы поговорим следующий раз. Вместо этого можно вернуть саму локальную переменную, при этом по умолчанию, для пользовательских типов, произойдет перемещение владения без дублирования ресурса.

#![allow(unused_assignments)]

fn main() {
    //&i32 - reference to i32
    let xx = 0;
    let r1_xx = &xx;
    println!("{}", r1_xx);

    //&mut i32 - reference to mutable i32
    let mut yy = 1;
    let r2_yy = &mut yy;
    //* - dereference
    *r2_yy = 2;
    println!("{}", r2_yy);

    //&i32 - mutable reference to i32
    let zz = 3;
    let mut r3_zz = &xx;
    r3_zz = &zz;
    println!("{}", r3_zz);

    let mut v = vec![1, 2, 3];
    //mutable reference as function argument
    foo(&mut v);
    println!("{:?}", v);
 
    let vec = vec![1, 2, 3, 4, 5];
    //reference to element
    for elem in vec.iter() { //elem: &i32 - reference
        print!("{} ", *elem); //* - explicit dereference
    }
}

//mutable reference as function parameter
fn foo(v: &mut Vec<i32>) {
    // i: &mut i32
    for i in v {
        //*i - dereference
        *i += 1;
    }
}

Перегуд В.

Комментариев нет:

Отправить комментарий