пятница, 20 декабря 2019 г.

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

Продолжаем. Экономия памяти, контроль висячих ссылок, перемещение владения по умолчанию, все эти правила повышают скорость работы, безопасность и надежность приложения. Но упрощают ли они работу программиста? И да, и нет. С одной стороны, мы сокращаем количество ошибок, которые нужно будет искать на тестировании и править на этапе поддержки. С другой стороны, это требует более продуманной архитектуры приложения и оригинального подхода в решении задач, т. к. старые проверенные решения не очень подходят. Есть ли возможность сделать переходный период более легким? Да. Точно так же, как в C++ можно соблюдать правила безопасности с помощью std::move() и умных указателей, в Rust есть возможность отойти от них и писать не безопасный код при необходимости.

Сегодня мы рассмотрим управление копированием в Rust. Это нужно не только для упрощения архитектуры и снижения безопасности, но также это нужно для правильной работы копирования в ситуациях, когда это необходимо. Так же как в C++ есть конструктор перемещения, в Rust есть trait (интерфейс) копирования, который позволяет явно или не явно производить копирование ресурсов. Обращаю ваше внимание, что все сказанное далее будет относиться к пользовательским типам. Также смотрите первую часть статьи, предыдущий пост.

Первичным является имплементация интерфейса Clone для явного копирования, это позволяет нам копировать ресурсы например вот так let y = x.clone(); В этом выражении явно указано, что мы копируем, и это облегчает чтение и понимание кода. Без .clone() будет происходить перемещение. Но мы можем пойти еще дальше, и имплементировать интерфейс Copy, который в свою очередь требует наличия Clone. Тогда в let y = x; копирование будет происходить не явно вместо перемещения и .clone() не требуется. Более того в простых ситуациях, мы можем не имплементировать эти интерфейсы самостоятельно, а наследовать их с помощью атрибута #[derive(Clone, Copy)]. И все будет работать как описано выше. Смотрим пример.

point.rs
--------
//!Custom type Point
///attributes
#[derive(Debug)]
//#[derive(Clone)]
//#[derive(Copy)]
///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
    }
}
///implement trait Clone
impl Clone for Point{
    fn clone(&self) -> Self {
        Point{
            x: self.x,
            y: self.y
        }
    }
}
///implement trait Copy
impl Copy for Point{ }

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

fn plus(a: i32, b: i32) -> i32 { //copying to
    a + b //copying 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); //OK!!!

    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); //OK!!!

    //attribute #[derive(Clone)] for Point
    //let p4 = p3.sum(p3.clone()); //clone explicit
    //println!("{:?}", p4); //OK!!!

    //attribute #[derive(Copy)] for Point
    //println!("{:?}", p1); //OK!!! after implicit copy in: let p2 = p1;
}

Тип String из стандартной библиотеки Rust, имплементирует только интерфейс Clone, поэтому может быть либо перемещен, либо явно скопирован с помощью .clone()

fn main() {
    let mut my_string = String::from("Hello, world!");

    foo(my_string.clone()); //COPY
    println!("{} ", my_string); //OK!

    foo(my_string); //MOVE
    //println!("{} ", my_string); //ERROR!
}

fn foo(s: String) -> String {
    println!("{} ", s);
    s
}

Если мы хотим клонировать вектор пользовательских типов (Vec<Point>), то должны либо сами имплементировать интерфейс Clone, либо наследовать его с помощью атрибута. Для вектора базовых типов (Vec<i32>) этого делать не нужно.

#[derive(Debug, Clone)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p1 = Point { x: 0, y: 0 };
    let p2 = Point { x: 5, y: 5 };

    let points = vec![p1.clone(), p2];
    foo(points.clone());
    println!("points: {:?} ", points); //OK
    println!("p1: {:?} ", p1); //OK
    //println!("p2: {:?} ", p2); //ERROR
}
fn foo(v: Vec<Point>) {
    println!("points: {:?} ", v);
}

Перегуд В.

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

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