Ответ 1
Прежде всего, все перечисленные вами предметы действительно разные, даже если они связаны с указателями. Box
- тип интеллектуального указателя, определенный библиотекой; ref
- синтаксис для сопоставления шаблонов; &
является ссылочным оператором, удвоенным как сиг- нал в ссылочных типах; *
- это оператор разыменования, удваивающий как сиг- нал в типах необработанных указателей. Подробнее см. Ниже.
В Rust существует четыре основных типа указателей, которые можно разделить на две группы: ссылки и необработанные указатели:
&T - immutable (shared) reference
&mut T - mutable (exclusive) reference
*const T - immutable raw pointer
*mut T - mutable raw pointer
Разница между двумя последними очень тонкая, потому что либо может быть отлита в другую без каких-либо ограничений, поэтому const
/mut
различие там в основном используется как линт. Raw указатели могут быть созданы свободно для чего угодно, и они также могут быть созданы из тонкого воздуха из целых чисел, например.
Естественно, это не так для ссылок - ссылочные типы и их взаимодействие определяют одну из ключевых особенностей Rust, заимствования. Ссылки имеют множество ограничений на то, как и когда они могут быть созданы, как их можно использовать и как они взаимодействуют друг с другом. В свою очередь они могут использоваться без блоков unsafe
. То, что заимствование точно и как оно работает, выходит за рамки этого ответа.
Обе ссылки и исходные указатели могут быть созданы с помощью оператора &
:
let x: u32 = 12;
let ref1: &u32 = &x;
let raw1: *const u32 = &x;
let ref2: &mut u32 = &mut x;
let raw2: *mut u32 = &mut x;
Обе ссылки и необработанные указатели могут быть разыменованы с помощью оператора *
, хотя для необработанных указателей требуется блок unsafe
:
*ref1; *ref2;
unsafe { *raw1; *raw2; }
Оператор разворота часто опускается, потому что другой оператор, называемый dot .
, автоматически ссылается или разделяет его левый аргумент. Так, например, если мы имеем эти определения:
struct X { n: u32 };
impl X {
fn method(&self) -> u32 { self.n }
}
то, несмотря на то, что method()
принимает self
по ссылке, self.n
автоматически разыгрывает его, поэтому вам не нужно набирать (*self).n
. Аналогичная ситуация возникает при вызове method()
:
let x = X { n: 12 };
let n = x.method();
Здесь компилятор автоматически ссылается на x
в x.method()
, поэтому вам не нужно писать (&x).method()
.
Рядом с последним фрагментом кода также был продемонстрирован специальный синтаксис &self
. Это означает только self: &Self
, или, более конкретно, self: &X
в этом примере. &mut self
, *const self
, *mut self
также работают.
Итак, ссылки являются основным видом указателя в Rust и должны использоваться почти всегда. Необработанные указатели, которые не имеют ограничений ссылок, должны использоваться в низкоуровневом коде, реализующем абстракции высокого уровня (коллекции, интеллектуальные указатели и т.д.) И в FFI (взаимодействующие с библиотеками C).
Rust также имеет динамически размерные (или нестандартные) типы. Эти типы не имеют определенного статически известного размера и поэтому могут использоваться только с помощью указателя/ссылки, однако недостаточно только указателя - необходима дополнительная информация, например длина для фрагментов или указатель на виртуальные методы таблица для объектов признаков. Эта информация "встроена" в указатели на нестандартные типы, делая эти указатели "жирными".
Толстый указатель в основном представляет собой структуру, которая содержит фактический указатель на кусок данных и некоторую дополнительную информацию (длина для срезов, указатель на vtable для объектов-признаков). Важно то, что Rust обрабатывает эти сведения о содержимом указателя абсолютно прозрачно для пользователя - если вы передадите значения &[u32]
или *mut SomeTrait
вокруг, автоматически будет передана соответствующая внутренняя информация.
Box<T>
является одним из интеллектуальных указателей в стандартной библиотеке Rust. Он обеспечивает способ выделения достаточной памяти в куче для хранения значения соответствующего типа, а затем он служит в качестве дескриптора, указателя на эту память. Box<T>
владеет данными, на которые указывает; когда он отбрасывается, соответствующий кусок памяти в куче освобождается.
Очень полезный способ думать о коробках - считать их регулярными значениями, но с фиксированным размером. То есть Box<T>
эквивалентно просто T
, за исключением того, что он всегда принимает несколько байтов, которые соответствуют размеру указателя на вашем компьютере. Мы говорим, что (принадлежащие) коробки предоставляют семантику значений. Внутренне они реализуются с использованием исходных указателей, как и любая другая абстракция высокого уровня.
Box
es (на самом деле это верно для почти всех других интеллектуальных указателей, таких как Rc
) также можно заимствовать: вы можете получить &T
из Box<T>
. Это может произойти автоматически с помощью оператора .
или вы можете сделать это явно путем разыменования и ссылки на него снова:
let x: Box<u32> = Box::new(12);
let y: &u32 = &*x;
В этом отношении Box
es аналогичны встроенным указателям - вы можете использовать оператор разыменования для достижения их содержимого. Это возможно, потому что оператор разыменования в Rust является перегружаемым, и он перегружен для большинства (если не всех) типов интеллектуальных указателей. Это позволяет легко заимствовать содержимое этих указателей.
И, наконец, ref
- это просто синтаксис в шаблонах для получения переменной ссылочного типа вместо значения. Например:
let x: u32 = 12;
let y = x; // y: u32, a copy of x
let ref z = x; // z: &u32, points to x
let ref mut zz = x; // zz: &mut u32, points to x
В то время как приведенный выше пример можно переписать с помощью ссылочных операторов:
let z = &x;
let zz = &mut x;
(что также делает его более идиоматичным), бывают случаи, когда ref
незаменимы, например, при использовании ссылок на варианты перечисления:
let x: Option<Vec<u32>> = ...;
match x {
Some(ref v) => ...
None => ...
}
В приведенном выше примере x
заимствован только внутри всего оператора match
, что позволяет использовать x
после этого match
. Если мы напишем его как таковой:
match x {
Some(v) => ...
None => ...
}
то x
будет потребляться этим match
и станет непригодным после него.