Думаю, следует начать с инкапсуляции. В общих чертах это объединение данных и методов в пользовательские типы. Обычно принято делать данные закрытыми и получать к ним доступ через открытые функции, с помощью которых гарантируется правильное изменение этих данных. Например, в C++ пользовательскими типами являются классы, и для разделения на закрытые и открытые члены используются блоки private и public. Целью этого является не столько улучшить жизнь программисту, сколько закрыть библиотеки от пользователей. И тут у одних заголовочные файлы, у других все на наследуемых классах. И первые, и вторые пилят систему модулей, но у них еще долгий путь, так как стандартные библиотеки написаны без учета модулей, тоже самое можно сказать про старые фреймворки. Но лучше поздно чем никогда.
Наверно нам повезло, т.к. в Rust уже есть система модулей. Но система модулей — это не только пространство имен для определения доступа, и это не только единица компиляции исходного кода, но и единица для динамического анализатора кода, что в свою очередь позволяет более удобно писать код. Например, без необходимости объявлять функцию до ее использования. Это делать не обязательно, но это пример того, что код анализируется более глубоко, что и доказывает компилятор, давая различные подсказки и точно указывая на ошибки. Замечу, что в Rust нет перегрузки функций. Чтобы нам закрыть данные, нужно поместить наши пользовательские типы в блок модуля с помощью "mod", который станет новым пространством имен. При обращении к этим данным понадобится указывать имя модуля "::" или импортировать "use" его в текущий модуль. Текущим модулем является текущий файл с исходным кодом, и как вы поняли, все другие файлы с исходным кодом являются модулями с именами файлов. По умолчанию сам модуль и все данные и функции в модуле являются закрытыми, и чтобы их открыть нужно указать их как “pub”. Таким образом мы можем открыть модуль для доступа извне, открыть тип, его данные и методы по отдельности.
Теперь давайте рассмотрим, как создать пользовательский тип в Rust. Сначала нам нужен набор данных, который будет хранить состояние объекта нашего типа. Это может быть структура с полями, например, координаты точки х и у. Чтобы привязать к этим данным функции, которые станут методами нашего типа, нужно поместить их в блок “impl”. Методы можно вызывать из объекта (с доступом к состоянию объекта) или из типа. Чтобы вызвать из объекта, нужно передать объект “self” (себя) или ссылку на него “&self” (ссылка на себя) в качестве первого параметра метода. Если данные представлены базовыми типами, то произойдет копирование значений в метод, если пользовательскими, то перемещение. По ссылке владение не передается. Если мы хотим изменить значение полей, то ссылка должна быть модифицирующей “&mut self”. Вызов метода из типа делается с помощью ::. Если мы хотим вернуть из метода тип, методом которого являемся, то можно использовать не имя структуры, а “Self” (Себя). Доступ к состоянию объекта (полям) осуществляется с помощью оператора точка “self.”.
Чтобы создавать и уничтожать объекты, в других языках существуют специальные методы: конструкторы и деструкторы. В Rust их нет. Тут работают стандартные правила. При выходе из пространства видимости, объект уничтожается и память освобождается. Для создания объекта по определенным правилам можно использовать методы с именами по договоренности, например метод new(), выполняет функции конструктора по умолчанию, а метод с параметрами from(), выполняет функции пользовательского конструктора или конструкторов копирования-перемещения. Обратите внимание, что определение копирования-перемещения задается не с помощью параметров при определении метода, а с помощью аргументов, с использованием clone() или без, при вызове. Смотрим пример.
//module
mod point {
//private data
#[derive(Debug, Clone)]
pub(crate) struct Point {
x: i32,
y: i32,
}
//public methods
impl Point {
pub fn new() -> Self {
Point { x: 0, y: 0 }
}
pub fn from(p: Point) -> Self {
Point { x: p.x, y: p.y }
}
pub fn get_xy(&self) -> String{
let a = self.x.to_string();
let b = self.y.to_string();
format!("X:{} Y:{}", a, b)
}
pub fn set_xy(&mut self, x:i32, y:i32){
self.x = x;
self.y = y;
}
}
}
//namespace
use point::Point;
fn main() {
//Error!
//let p0 = point::Point { x: 1, y: 1 };
//println!("{:?}", p0);
//default constructor
let p1 = point::Point::new();
println!("{:?}", p1);
//copy assign
let p2 = p1.clone();
println!("{:?}", p2);
//move assign
let p3 = p2;
println!("{:?}", p3);
//copy constructor
let p4 = Point::from(p3.clone());
println!("{:?}", p4);
//move constructor
let mut p5 = Point::from(p3);
println!("{:?}", p5);
//methods
p5.set_xy(3,3);
println!("P5<{}>", p5.get_xy());
}
Перегуд В.
Комментариев нет:
Отправить комментарий