Как обойти сосуществование изменчивого и неизменного заимствования? - PullRequest
0 голосов
/ 08 октября 2018

У меня есть Context struct:

struct Context {
    name: String,
    foo: i32,
}

impl Context {
    fn get_name(&self) -> &str {
        &self.name
    }
    fn set_foo(&mut self, num: i32) {
        self.foo = num
    }
}

fn main() {
    let mut context = Context {
        name: "MisterMV".to_owned(),
        foo: 42,
    };
    let name = context.get_name();
    if name == "foo" {
        context.set_foo(4);
    }
}

В функции мне нужно сначала получить name из context и обновить foo в соответствии с name Ihave:

let name = context.get_name();
if (name == "foo") {
    context.set_foo(4);
}

Код не скомпилируется, потому что get_name() занимает &self, а set_foo() - &mut self.Другими словами, у меня есть неизменный заем на get_name(), но у меня также есть изменчивый заем на set_foo() в той же области, что противоречит правилам ссылок .

В любой момент времени у вас может быть одна (но не обе) одна изменяемая ссылка или любое количество неизменяемых ссылок.

Ошибка выглядит следующим образом:

error[E0502]: cannot borrow `context` as mutable because it is also borrowed as immutable
  --> src/main.rs:22:9
   |
20 |     let name = context.get_name();
   |                ------- immutable borrow occurs here
21 |     if name == "foo" {
22 |         context.set_foo(4);
   |         ^^^^^^^ mutable borrow occurs here
23 |     }
24 | }
   | - immutable borrow ends here

Мне интересно, как мне обойти эту ситуацию?

Ответы [ 3 ]

0 голосов
/ 08 октября 2018

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

Более простое решение - использовать #![feature(nll)], это скомпилируется без проблем.

Но вы можете решить проблему без nll, используя простое совпадение, подобное этому:

fn main() {
    let mut context = Context {
        name: "MisterMV".to_owned(),
        foo: 42,
    };
    match context.get_name() {
        "foo" => context.set_foo(4),
        // you could add more case as you like
        _ => (),
    }
}
0 голосов
/ 08 октября 2018

Прежде чем увидеть комментарий @ Stargateur, я придумал нижеприведенный текст, который прекрасно компилируется, но клонирует строку имени:

struct Context {
    name: String,
    foo: i32,
}

impl Context {
    fn get_name(&self) -> String {
        self.name.clone()
    }
    fn set_foo(&mut self, num: i32) {
        self.foo = num
    }
}

fn main() {
    let mut context = Context {
        name: String::from("bill"),
        foo: 5,
    };

    let name = context.get_name();
    if name == "foo" {
        context.set_foo(4);
    }
    println!("Hello World!");
}

Работая с образцом @ Stargateur, оказывается, что это удивительно просторешение этой конкретной проблемы - в сочетании с get_name с if, например,

struct Context {
    name: String,
    foo: i32,
}

impl Context {
    fn get_name(&self) -> &String {
        &self.name
    }
    fn set_foo(&mut self, num: i32) {
        self.foo = num
    }
}

fn main() {
    let mut context = Context {
        name: "MisterMV".to_owned(),
        foo: 42,
    };
    if context.get_name() == "foo" {
        context.set_foo(4);
    }
}

Я считаю, что это потому, что переменная для части get_name имеет четко определенное время жизни, тогда как когдапеременная name была отдельной, по сути ее значение могло внезапно измениться без явной мутации.

0 голосов
/ 08 октября 2018

Это очень широкий вопрос.Средство проверки заимствований является, пожалуй, одной из самых полезных функций Rust, но в то же время и самой хитрой.Улучшения в эргономике делаются регулярно, но иногда такие ситуации случаются.

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

I,Преобразование в форму, которая требует только ограниченного заимствования

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

if context.get_name() == "foo" {
    context.set_foo(4);
}

Срок действия заимствования истекает в операторе if.Этот обычно - это путь, по которому вы хотите идти, и по мере того, как улучшаются такие функции, как нелексические времена жизни, эта опция становится более приемлемой.Например, способ, которым вы сейчас написали, будет работать, когда NLL будут доступны, потому что эта конструкция должным образом определена как «ограниченный заем»!Переформулировка иногда завершается неудачей по странным причинам (особенно если для оператора требуется соединение изменяемых и неизменных вызовов), но это должен быть ваш первый выбор.

II.Используйте взламывание областей действия с выражениями как операторы

let name_is_foo = {
    let name = context.get_name();
    name == "foo"
};

if name_is_foo {
    context.set_foo(4);
}

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

III.Создайте «метод слияния» для типа

   impl Context {
      fn set_when_eq(&mut self, name: &str, new_foo: i32) {
          if self.name == name {
              self.foo = new_foo;
          }
      }
   }

Конечно, есть бесконечные варианты этого.Самым общим является функция, которая принимает fn(&Self) -> Option<i32> и устанавливает на основе возвращаемого значения этого замыкания (None для "not set", Some(val) для установки этого значения).

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

IV.Клон

let name = context.get_name().clone();
if name == "foo" {
    context.set_foo(4);
}

Иногда нужно сделать быстрый клон.Избегайте этого, когда это возможно, но иногда стоит просто куда-то добавить clone() вместо того, чтобы тратить 20 минут, пытаясь выяснить, как, черт возьми, заставить ваши кредиты работать.Зависит от вашего крайнего срока, того, насколько дорогим является клон, как часто вы вызываете этот код и т. Д.

Например, возможно чрезмерное клонирование PathBuf с в приложениях CLI не является ужасно редким явлением.

V.Используйте unsafe ( НЕ РЕКОМЕНДУЕТСЯ )

let name: *const str = context.get_name();
unsafe{
    if &*name == "foo" {
        context.set_foo(4);
    }
}

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

Имейте в виду, что компилятор ожидает, что небезопасный код, который вы пишете, поддерживает все гарантии, необходимые для безопасного кода Rust.Блок unsafe указывает, что, хотя компилятор не может проверить, что код безопасен, программист имеет.Если программист не правильно проверил это, компилятор, скорее всего, создаст код, содержащий неопределенное поведение, которое может привести к небезопасной памяти, сбоям и т. Д., Многим из которых Rust стремится избежать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...