Как интерпретировать неизменные ссылки на изменяемые типы в Rust? - PullRequest
2 голосов
/ 06 мая 2019

Кажется, что я не могу ничего мутировать, если в моей цепочке разыменования есть любая неизменная ссылка. Образец:

fn main() {
    let mut x = 42;
    let y: &mut i32 = &mut x; // first layer
    let z: &&mut i32 = &y; // second layer
    **z = 100; // Attempt to change `x`, gives compiler error.

    println!("Value is: {}", z);
}

Я получаю сообщение об ошибке компилятора:

error[E0594]: cannot assign to `**z` which is behind a `&` reference
 --> src/main.rs:5:5
  |
4 |     let z: &&mut i32 = &y; // second layer
  |                        -- help: consider changing this to be a mutable reference: `&mut y`
5 |     **z = 100; // Attempt to change `x`, gives compiler error.
  |     ^^^^^^^^^ `z` is a `&` reference, so the data it refers to cannot be written

В некотором смысле это имеет смысл, так как в противном случае компилятор не сможет предотвратить наличие нескольких изменяемых путей доступа к одной и той же переменной.

Однако при рассмотрении типов семантика кажется нелогичной:

  • Переменная y имеет тип &mut i32 или на простом английском языке "Изменяемая ссылка на целое число".
  • Переменная z имеет тип &&mut i32, или на простом английском языке «Неизменная ссылка на изменяемую ссылку на целое число».
  • Разыменовывая z один раз (то есть *z), я получу что-то типа &mut i32, то есть что-то типа того же типа, что и y. Однако, разыменование этого снова (т.е. **z) дает мне что-то типа i32, но мне не разрешено изменять это целое число.

По сути, типы ссылок в некотором смысле лгут мне, поскольку они на самом деле не делают то, что утверждают, что делают. Как мне правильно читать типы ссылок в этом случае или как еще я могу восстановить веру в эту концепцию?

Тестирование с этим образцом:

fn main() {
    let mut x = 42;
    let y: &mut i32 = &mut x; // first layer
    let m: &&mut i32 = &y; // second layer
    let z: &&&mut i32 = &m; // third layer
    compiler_builtin_deref_first_layer(*z);
}

fn compiler_builtin_deref_first_layer(v: &&mut i32) {
    compiler_builtin_deref_second_layer(*v);
}

fn compiler_builtin_deref_second_layer(w: &mut i32) {
    println!("Value is: {}", w);
}

Типы параметров этих двух последних функций верны. Если я изменю любой из них, компилятор будет жаловаться на несовпадающие типы. Однако, если я скомпилирую пример как есть, я получу эту ошибку:

error[E0596]: cannot borrow `**v` as mutable, as it is behind a `&` reference

Почему-то звонок на compiler_builtin_deref_first_layer кажется нормальным, но звонок на compiler_builtin_deref_second_layer - нет. Ошибка компилятора говорит о **v, но я вижу только *v.

1 Ответ

2 голосов
/ 08 мая 2019

По сути, типы ссылок в некотором смысле лгут мне, поскольку они на самом деле не делают то, что утверждают, что делают. Как мне правильно читать типы ссылок в этом случае или как еще я могу восстановить веру в эту концепцию?

Правильный способ чтения ссылок в Rust - это разрешения.

Право собственности на объект, если оно не заимствовано, дает вам разрешение делать с объектом все, что вы хотите; создать его, уничтожить его, переместить его из одного места в другое. Вы владелец, вы можете делать то, что хотите, вы контролируете жизнь этого объекта.

Изменяемая ссылка заимствует объект у владельца. Пока изменяемая ссылка жива, она предоставляет монопольный доступ к объекту. Никто не может читать, писать или делать что-либо еще с объектом. Изменяемая ссылка также может быть вызовом и эксклюзивной ссылкой или эксклюзивным заимствованием. Вы должны вернуть контроль над объектом первоначальному владельцу, но тем временем вы можете делать с ним все, что захотите.

Неизменная ссылка или общий заем означает, что вы получаете доступ к ней одновременно с другими. Из-за этого вы можете только прочитать его, и никто не может изменить его, иначе будут неопределенные результаты, основанные на точном порядке, в котором произошли действия.

Как изменяемые (или исключительные) ссылки, так и неизменяемые (или общие) ссылки могут быть сделаны для принадлежащих объектов, но это не означает, что вы владеете объектом, когда ссылаетесь на него через ссылку. То, что вы можете делать с объектом, зависит от того, через какую ссылку вы к нему обращаетесь.

Так что не думайте о ссылке &&mut T как о «неизменной ссылке на изменяющуюся ссылку на T», а затем подумайте: «Ну, я не могу изменить внешнюю ссылку, но я должен иметь возможность изменять мутацию внутренняя ссылка. "

Вместо этого думайте об этом как о "Кто-то владеет T. Они предоставили эксклюзивный доступ, поэтому прямо сейчас есть кто-то, кто имеет право изменять T. Но в то же время, этот человек дал общий доступ к &mut T, что означает, что они обещали не изменять его в течение определенного периода времени, и все пользователи могут использовать общую ссылку на &mut T, включая разыменование базового T, но только для вещей, которые вы обычно можете делать с общей ссылкой, что означает чтение, но не запись. "

Последнее, что нужно иметь в виду, это то, что изменяемая или неизменяемая часть не является принципиальной разницей между ссылками. Это действительно эксклюзивная и общая часть. В Rust вы можете изменить что-то с помощью общей ссылки, если есть какой-то механизм внутренней защиты, который гарантирует, что только один человек может сделать это одновременно. Есть несколько способов сделать это, например, Cell, RefCell или Mutex.

То, что предоставляют &T и &mut T, на самом деле не является неизменным или изменяемым доступом, хотя они названы так, потому что это уровень доступа по умолчанию, который они предоставляют на уровне языка при отсутствии каких-либо библиотечных функций. Но на самом деле они предоставляют общий или монопольный доступ, и тогда методы для типов данных могут предоставлять вызывающим функции различные функции в зависимости от того, принимают ли они собственное значение, монопольную ссылку или общую ссылку.

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

...