Подтвердить что ты не робот

Понимание типов указателей в ржавчине

Я немного смущен тем, как работают указатели в Rust. Там ref, Box, &, *, и я не уверен, как они работают вместе.

Вот как я понимаю это в настоящее время:

  • Box на самом деле не является указателем - это способ распределения данных в куче и пропускать в аргументах функции нестандартные типы (особенно черты).
  • ref используется в сопоставлении с образцом, чтобы брать что-то, на что вы согласны, вместо того, чтобы принимать его. Например,

    let thing: Option<i32> = Some(4);
    match thing {
        None => println!("none!"),
        Some(ref x) => println!("{}", x), // x is a borrowed thing
    }
    println!("{}", x + 1); // wouldn't work without the ref since the block would have taken ownership of the data
    
  • & используется для создания заимствования (заимствованного указателя). Если у меня есть функция fn foo(&self), то я беру ссылку на себя, которая истечет после завершения функции, оставив данные вызывающего абонента в покое. Я также могу передать данные, которые я хочу сохранить, выполнив bar(&mydata).

  • * используется для создания необработанного указателя: например, let y: i32 = 4; let x = &y as *const i32. Я понимаю указатели на C/С++, но я не уверен, как это работает с системой типа Rust и как их можно безопасно использовать. Я также не знаю, какие варианты использования для этого типа указателя. Кроме того, символ * может использоваться для разыменования вещей (что и почему?).

Может ли кто-нибудь объяснить мне 4-й тип указателя и убедиться, что мое понимание других типов верное? Я также хотел бы, чтобы кто-нибудь указывал на случаи общего использования, о которых я не упоминал.

4b9b3361

Ответ 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 и станет непригодным после него.

Ответ 2

Box логически является новым типом вокруг исходного указателя (*const T). Тем не менее, он выделяет и освобождает свои данные во время строительства и уничтожения, поэтому ему не нужно брать данные из какого-либо другого источника.

То же самое относится к другим типам указателей, например Rc - указатель с подсчетом ссылок. Это структуры, содержащие частные исходные указатели, которые они выделяют и освобождают от.

Необработанный указатель имеет точно такой же формат, что и обычный указатель, поэтому в некоторых случаях несовместимы с указателями C. Важно отметить, что *const str и *const [T] - указатели жира, что означает, что они содержат дополнительную информацию о длине значения.

Однако исходные указатели не дают никаких гарантий относительно их действительности. Например, я могу безопасно выполнять

123 as *const String

Указатель недействителен, поскольку ячейка памяти 123 не указывает на допустимый String. Таким образом, при разыменовании одного требуется блок unsafe.

Кроме того, в то время как заимствования необходимы для соблюдения определенных законов, а именно, что вы не можете иметь несколько заимствований, если один из них изменен - ​​необработанные указатели не должны это уважать. Существуют и другие, более слабые законы, которые должны соблюдаться,, но вы вряд ли столкнетесь с этим.

Нет логической разницы между *mut и *const, хотя их, возможно, нужно будет отдать другим, чтобы выполнить определенные операции - различие является документальным.

Ответ 3

Ссылки и исходные указатели - это то же самое на уровне реализации. Отличие от перспективы программиста заключается в том, что ссылки безопасны (в условиях ржавчины), необработанные указатели. Контроллер заимствования гарантирует, что ссылки всегда действительны (управление жизненным циклом), что вы можете иметь только одну изменяемую ссылку во время и т.д.

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

Контейнеры в стандартной библиотеке реализуются с использованием необработанных указателей, Box и Rc.

Box и Rc - это то, что интеллектуальные указатели находятся на С++, это обертки вокруг необработанных указателей.