Почему Rust допускает неизменную ссылку на изменяемую переменную? - PullRequest
0 голосов
/ 27 октября 2019

Я работаю над Rust Book (глава 4) и удивляюсь, что подобный код компилируется :

fn main() {
    let mut s = String::from("hello");

    let r1 = &s;
    let r2 = &s;
    println!("{}, {}", r1, r2);

    // this line silences the warning: 'variable does not need to be mutable'
    s.push_str(" world");
}

Почему Rust допускает неизменную ссылку на изменяемый файлпеременная? Это может ослабить гарантии безопасности. Если у меня есть изменяемая переменная, и я передаю неизменные ссылки некоторым потокам, эти потоки предполагают, что значение не изменится, но я мог бы изменить значение через исходную переменную.

Я еще не достиг многопоточности, но нашел это странным, и в этом случае ничем не отличается от C ++:

void doNotChangeMyString(const std::string& myConstString) {
  // ... assume that myConstString cannot change and use it on a thread
  // return immediately even though some worker thread is still
  // using myConstString
}

void main() {
    std::string s = "hello" // not const!
    doNotChangeMyString(s);
    s = "world"; // oops
}

Редактировать: я исправил код Rust, чтобы он компилировался. Пожалуйста, пересмотрите отрицательные голоса и закройте голоса. Принятый ответ объясняет концепцию, которую я не получил из главы Rust Book о заимствованиях, она была очень полезна для меня и могла помочь другим, кто находится на том же этапе изучения Rust.

1 Ответ

4 голосов
/ 27 октября 2019

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

let mut foo = String::new();
let foo = foo;
let mut foo = foo;

foo внезапно становится неизменным, но это не означает, что первые два foo s не существуют.

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

let mut my_string = String::new();
my_string.push_str("This is ok! ");
let foo: &mut String = &mut my_string;
foo.push_str("This goes through the mutable reference, and is therefore ok! ");
my_string.push_str("This is not ok, and will not compile because `foo` still exists");
println!("We use foo here because of non lexical lifetimes: {:?}", foo);

Второй вызов my_string.push_str не будет компилироваться, потому что foo может (в этом случае гарантированно) использоваться позже.

Ваш конкретный вопрос задает нечто похожее на следующее, но вам даже не нужна многопоточность, чтобы проверить это:

fn immutably_use_value(x: &str) {
    println!("{:?}", x);
}

let mut foo = String::new();
let bar = &foo; //This now has immutable access to the mutable object.
let baz = &foo; //Two points are allowed to observe a value at the same time. (Ignoring `Sync`)
immutably_use_value(bar); //Ok, we can observe it immutably
foo.push_str("Hello world!"); //This would be ok... but we use the immutable references later!
immutably_use_value(baz);

Это не компилируется. Если вымогли бы аннотировать время жизни, они выглядели бы примерно так:

let mut foo = String::new();  //Has lifetime 'foo
let bar: &'foo String = &foo; //Has lifetime 'bar: 'foo
let baz: &'foo String = &foo; //Has lifetime 'baz: 'foo
//On the other hand:
let mut foo = String::new();          //Has lifetime 'foo
let bar: &'foo mut String = &mut foo; //Has lifetime 'bar: mut 'foo
let baz: &'foo mut String = &mut foo; //Error, we cannot have overlapping mutable borrows for the same object!

Несколько дополнительных заметок:

  • Из-за NLL (Non Lexical Lifetimes), будет скомпилирован следующий код:

    let mut foo = String::new();
    let bar = &foo;
    foo.push_str("Abc");
    

    Поскольку bar не используется после изменяемого использования foo.

  • Вы упомянули многопоточность, котораяимеет свои собственные ограничения и черты:

    Черта Send позволит вам передать права собственности на переменную в потоке.

    Sync черта позволит вам поделиться ссылкой на переменную в потоке. Это включает изменяемые ссылки, если исходный поток не использует объект в течение срока заимствования.

    Несколько примеров:

    • Тип T равен Send + Sync, он может быть отправлен между потоками и может быть распределен между ними
    • Тип T равен !Send + Sync, он может быть разделен между потоками, но не отправлен между ними. Примером является дескриптор окна, который может быть уничтожен только в исходном потоке.
    • Тип T равен Send + !Sync, его можно отправлять по потокам, но не делить между ними. Примером является RefCell, который может использовать только проверку заимствования во время выполнения для одного потока, поскольку он не использует атомарность (многопоточные безопасные компоненты).
    • Тип T is!Send + !Sync, он может жить только в том потоке, в котором он был создан. Примером является Rc, который не может отправить свою копию между потоками, потому что он не может подсчитывать ссылки атомарно (посмотрите на Arc, чтобы сделать это), и так как он не несет времени жизничтобы заставить одну копию себя существовать при отправке через границу потока, поэтому она не может быть отправлена ​​через потоки.
  • Я использую &str вместо &String в моем третьем примере, это потому, что String: Deref<str> (вам может понадобиться прокрутить вниз, чтобы увидеть его)и, следовательно, везде, где мне нужно &str, я могу добавить &String, потому что компилятор автоматически определит его.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...