Определение методов
Поменяем функцию area
, которая имеет экземпляр Rectangle
в качестве параметра, и вместо этого создадим метод area
, определенный для структуры Rectangle
, как показано в Листинге 5-13.
Имя файла: src/main.rs
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
1 impl Rectangle {
2 fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!(
"The area of the rectangle is {} square pixels.",
3 rect1.area()
);
}
Листинг 5-13: Определение метода area
для структуры Rectangle
Для определения функции в контексте Rectangle
мы начинаем блок impl
(реализации) для Rectangle
[1]. Все, находящееся внутри этого блока impl
, будет связано с типом Rectangle
. Затем мы перемещаем функцию area
внутри фигурных скобок impl
[2] и меняем первый (и в этом случае единственный) параметр на self
в сигнатуре и во всем теле. В main
, где мы вызывали функцию area
и передавали rect1
в качестве аргумента, мы можем вместо этого использовать синтаксис методов, чтобы вызвать метод area
на нашем экземпляре Rectangle
[3]. Синтаксис методов идет после экземпляра: мы добавляем точку, за которой следует имя метода, скобки и любые аргументы.
В сигнатуре для area
мы используем &self
вместо rectangle: &Rectangle
. &self
на самом деле является сокращением для self: &Self
. Внутри блока impl
тип Self
является псевдонимом для типа, для которого предназначен блок impl
. Методы должны иметь параметр с именем self
типа Self
в качестве своего первого параметра, поэтому Rust позволяет вам сократить это до только имени self
в первом месте параметра. Обратите внимание, что мы по-прежнему должны использовать &
перед сокращением self
, чтобы указать, что этот метод берет в долг экземпляр Self
, так же, как мы это делали в rectangle: &Rectangle
. Методы могут владеть self
, брать его в неизменяемом долге, как мы сделали здесь, или брать его в изменяемом долге, так же, как они могут любой другой параметр.
Мы выбрали &self
здесь по тем же причинам, по которым мы использовали &Rectangle
в версии функции: мы не хотим брать владение, а просто хотим прочитать данные в структуре, а не записывать в них. Если бы мы хотели изменить экземпляр, на котором мы вызвали метод, как часть того, что делает метод, мы бы использовали &mut self
в качестве первого параметра. Наряду с тем, что метод, который берет владение экземпляром, используя только self
в качестве первого параметра, редок, этот подход обычно используется, когда метод преобразует self
в что-то другое и вы хотите предотвратить вызовчика от использования исходного экземпляра после преобразования.
Основная причина использования методов вместо функций, помимо предоставления синтаксиса методов и избавления от необходимости повторять тип self
в каждой сигнатуре метода, заключается в организации. Мы поместили все, что можно сделать с экземпляром типа, в один блок impl
, а не заставляем будущих пользователей нашего кода искать возможности работы с Rectangle
в различных местах библиотеки, которую мы предоставляем.
Обратите внимание, что мы можем выбрать дать методу такое же имя, как одно из полей структуры. Например, мы можем определить метод для Rectangle
, который также называется width
:
Имя файла: src/main.rs
impl Rectangle {
fn width(&self) -> bool {
self.width > 0
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
if rect1.width() {
println!(
"The rectangle has a nonzero width; it is {}",
rect1.width
);
}
}
Здесь мы выбираем сделать метод width
возвращать true
, если значение в поле width
экземпляра больше 0
, и false
, если значение равно 0
: мы можем использовать поле внутри метода с тем же именем для любой цели. В main
, когда мы пишем rect1.width
с последующими скобками, Rust понимает, что мы имеем в виду метод width
. Когда мы не используем скобки, Rust понимает, что мы имеем в виду поле width
.
Чаще всего, но не всегда, когда мы даем методу такое же имя, как и полю, мы хотим, чтобы он только возвращал значение в поле и ничего другого не делал. Методы такого типа называются геттерами, и Rust не реализует их автоматически для полей структуры, как это делает некоторые другие языки. Геттеры полезны, потому что вы можете сделать поле приватным, а метод публичным, и таким образом обеспечить доступ только для чтения к этому полю в качестве части публичного API типа. Мы обсудим, что такое публичное и приватное, и как указать поле или метод как публичный или приватный, в главе 7.
Где находится оператор ->?
В C и C++ для вызова методов используются два разных оператора: вы используете .
, если вызываете метод непосредственно на объекте, и ->
, если вызываете метод на указателе на объект и нужно сначала разыменовать указатель. Другими словами, если object
- это указатель, то object->
something()
аналогично (*object).
something()
.
Rust не имеет эквивалента оператора ->
; вместо этого Rust имеет функцию, называемую автоматическое ссылкирование и разыменование. Вызов методов - это одна из为数不多их ситуаций в Rust, где это поведение встречается.
Вот, как это работает: когда вы вызываете метод с использованием object.
something()
, Rust автоматически добавляет &
, &mut
или *
, чтобы object
соответствовал сигнатуре метода. Другими словами, следующие выражения эквивалентны:
p1.distance(&p2);
(&p1).distance(&p2);
Первый вариант выглядит гораздо чище. Это автоматическое поведение ссылкирования работает потому, что методы имеют четкого приемника - тип self
. С учетом приемника и имени метода Rust может точно определить, читает ли метод (&self
), изменяет ли (&mut self
) или потребляет (self
) экземпляр. Факт, что Rust делает заимствование неявным для приемников методов, является важной частью того, чтобы владение было удобным в практике.