На начальном у всех новичков возникают проблемы с тем что значит self в rust и чем он отличается от Self.
Ниже приведен пример кода из книги «Rust Профессиональное программирование». В книге допущена ошибка, я отметил ее в комментарии.
Ожидался self, но случайно был напечатан Self и программа перестала компилироваться. Посмотрите этот код, он отлично иллюстрирует сразу несколько особенностей языка, но раз уж речь пошла о self, обратите внимание как он используется в примере.
/*
* В этом списке отсутствуют операции вставки.
* Можно попробовать реализовать их самостоятельно.
* Список никогда не будет пустым, недействительным или содержать нулевые указатели.
*/
// В каждом элементе списка имеются упакованные в нем данные.
// Поле не может быть пустым или нулевым.
struct ListItem {
data: Box,
next: Option<Box<ListItem>>, // Значение None = конец списка
}
// Структура списка содержит только заголовок
// упаковка которого нас не волнует поскольку он обязателен
struct SinglyLinkedList {
head: ListItem
}
impl ListItem {
fn new(data: T) -> Self {
ListItem {
// Новые данные перемещаются в кучу при помощи Box
data: Box::new(data),
next: None
}
}
// Next - возвращает для каждого элемента опциональную ссылку
// если она существует. Нужен для упрощения кода распаковки элементов.
fn next(&self) -> Option<&Self> {
// проверка наличия связи для элемента
if let Some(next) = &self.next {
// Возвращение следующей ссылки на элемент
// Some(&*next) - эквивалентно
Some(next.as_ref())
} else {
None
}
}
fn mut_tail(&mut self) -> &mut Self {
// if let здесь не сработает, поскольку мы не можем одновременно
// заимствовать self.next и возвращать изменяемую ссылку на внутренний указатель.
if self.next.is_some() {
// Box внутри Options требует распаковки из изменяемой ссылки
// и вернуть изменяемую ссылку изнутри
self.next.as_mut().unwrap().mut_tail()
} else {
// Если элемента нет, это хвост и возвращаем self
self // В книге здесь Self
}
}
// Получаем
fn data(&self) -> &T {
self.data.as_ref()
}
}
impl SinglyLinkedList {
// Для нового списка нужен первый элемент. Чтобы допустить наличие
// пустого списка задан как Option()
fn new(data: T) -> Self {
SinglyLinkedList {
head: ListItem::new(data),
}
}
fn append(&mut self, data: T) {
let tail = self.head.mut_tail();
// Добавляем новый элемент в хвост
tail.next = Some(Box::new(ListItem::new(data)))
}
fn head(&self) -> &ListItem {
&self.head
}
}
fn main() {
let mut list = SinglyLinkedList::new("head");
list.append("middle");
list.append("tail");
let mut item = list.head();
loop {
println!("item: {}", item.data());
if let Some(next_item) = item.next() {
item = next_item;
} else {
break;
}
}
}
/*
* "Для новичка Rust связянный список будет отличной возможностью узнать об уникальных особенностях языка".
* Бренден Мэтьюз. Rust профессиональное программирование
*/
В Rust Self и self — это совершенно разные сущности, несмотря на схожесть написания. Разберём их по пунктам.
self (строчная буква)
Что это: первый параметр метода, представляющий экземпляр типа, для которого вызывается метод.
Особенности:
- Всегда пишется со строчной буквы.
- Является значением (конкретным объектом).
- Может иметь разные формы владения:
self— передаёт владение (объект «уходит» в метод);&self— неизменяемая ссылка (только чтение);&mut self— изменяемая ссылка (можно менять поля).
Self (заглавная буква)
Что это: ключевое слово, обозначающее текущий тип в контексте реализации.
Особенности:
- Пишется с заглавной буквы.
- Является типом, а не значением.
- Автоматически подставляется компилятором как синоним текущего типа.
- Особенно полезен в трейтах и при обобщённом программировании.
Следуя рекомендациям по синтаксису Rust мы знаем, что типажи и структуры пишутся с заглавной буквы, в то время как переменные принято писать со строчной. Когда втягиваешься, это оказывается удобно. Пример с S(s)elf явное тому доказательство.
В некоторых языках применяются this и self. И такой подход так же создает немало путаницы не только у новичков.
Так как же никогда не запутаться? Ответ прост: никак. В своем коде вы будете допускать подобные ошибки. К сожалению это неизбежно, когда для разных обстоятельств применяется одно слово и разница между совершенно разными сущностями — регистр заглавной буквы, ошибки неизбежны.
Чтобы отлавливать такие ошибки в больших массивах кода, научитесь читать сообщения об ошибках компилятора. Внимательно и вдумчиво изучая, что заставило компилятор вывести эти сообщения.
