четверг, 19 декабря 2019 г.

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

Синтаксически владение (ownership) ресурсом обозначается с помощью переменной и ее значения с учетом типа. Например в выражении: int х = 5;, владельцем становится переменная ”x”, которая имеет целочисленное значение 5, для которого в памяти выделено место определенного размера (соответствующего типа). Так как память ограничена по объему, она и является нашим ресурсом. Учитывая возможный размер пользовательских типов, которые могут содержать в себе большие данные, для которых потребуется значительный объем памяти, перед нами стоит задача в эффективном управлении ресурсами.

Смена владельца ресурса происходит, например при присваивании значения 5 второй переменной через первую переменную: int y = x; В этом случае происходит копирование (дублирование, клонирование) памяти, в которой размещается еще одно значение 5, владельцем которой становится переменная “y”. При этом переменная “x” никуда не девается, она доступна на протяжении своего времени жизни. Также передача владения происходит при передаче параметров в функцию и возвращении значения из нее. Такой подход (копирование) в управлении ресурсами используется по умолчанию в C/C++ для всех типов.

Если мы хотим экономно распоряжаться памятью, то нам может потребоваться перемещение ресурса от одного владельца к другому. Мы можем решить эту задачу с помощью указателей, ссылок или специальных инструментов для перемещения значения между переменными. При таком подходе дублирование занимаемой памяти не происходит, значение остается то же, но так как физически у области памяти может быть только одна переменная (владелец), которая указывает на эту область памяти, то при выполнении: int y = x; происходит очистка переменной “x”, после чего она уже не содержит значение 5 и не может быть использована в дальнейшем. Отслеживание такой ситуации, при котором при анализе кода перед компиляцией происходит ограничение в использовании очищенных переменных, является необходимым условием для стандарта безопасности типов. Если этого не делать, то возможны утечки памяти или ошибки, приводящие к неопределенному поведению, что и происходит в C/C++ при неправильном использовании “сырых” указателей.

В Rust по умолчанию для базовых типов используется копирование ресурсов, а для пользовательских типов используется перемещение ресурсов. Смотрим пример.

point.rs
--------
//!Custom type
#[derive(Debug)]
///Fields
pub struct Point {
    x: i32,
    y: i32,
}
///Methods
impl Point {
    ///constructor
    pub fn new() -> Self {
        Point { x: 0, y: 0 }
    }

    pub fn add(&self, n: i32) -> Self { //copying i32 to
        Point { x: self.x + n, y: self.y + n } //moving Point from
    }

    pub fn sum(&self, p: Point) -> Self{ //moving Point to
        Point {
            x: self.x + p.x,
            y: self.y + p.y
        } //moving Point from
    }
}

main.rs
-------
#![allow(unused_variables, unused_imports, dead_code)]
mod point;
use point::Point;

fn plus(a: i32, b: i32) -> i32 { //copying i32 to
    a + b //copying i32 from
}

fn main() {
    //ownership basic types
    let x: i32 = 5; //assign
    let y: i32 = x; //copying assign
    println!("{}", x); //x after copying

    let z = plus(x, y); //copying to and from fn
    println!("{} + {} = {}", x, y, z); //OK!!!

    //ownership custom types
    let p1 = Point::new(); //construct
    let p2 = p1; //p1 moving assign by default
    //println!("{:?}", p1); //ERROR: value borrowed here after move

    let p3 = p2.add(z); //copy i32 to method, moving Point from method
    println!("{:?} + {} = {:?}", p2, z, p3); //OK!!!

    let p4 = p2.sum(p3); //move p3 to method
    //println!("{:?}", p3); //ERROR: value borrowed here after move
}

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

fn foo_vec(mut v: Vec<i32>) -> Vec<i32> {
    for i in 0..v.len() {
        v[i] += 1;
    }
    println!("vec in foo: {:?}", v);
    v //MOVE
}

fn foo_arr(mut a: [i32; 3]) -> [i32; 3] {
    for i in 0..3 {
        a[i] += 1;
    }
    println!("arr in foo: {:?}", a);
    a //COPY
}

fn main() {

    println!("\n<<< Arrays >>>");
    //Arrays - copy by default
    let a1 = [0; 3];
    foo_arr(a1); //COPY
    println!("arr in main: {:?}", a1);

    println!("\n<<< Vectors >>>");
    //Vectors - move by default
    let v1 = vec![1, 2, 3];
    foo_vec(v1.clone()); //COPY explicit
    println!("vec in main: {:?}", v1);

    let v2 = vec![1, 2, 3];
    foo_vec(v2); //MOVE
    //println!("vec in main: {:?}", v2); //ERROR!

    let v3 = vec![1, 2, 3];
    let v4 = foo_vec(v3); //MOVE
    println!("vec in main: {:?}", v4); //OK!
}

Перегуд В.

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

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