У меня есть структура с функцией remove
, и я хочу, чтобы функция remove
возвращала новый экземпляр, если он действительно удален, или ту же ссылку.
Но я получаю Cannot move
, когда пытаюсь return *self
Является ли это возможным?
Судя по ошибкам, я так не думаю, но тогда как Cow
возможно.
Код:
use std::collections::HashMap;
#[derive(Clone)]
struct Something {
data: HashMap<char, u16>,
}
impl Something {
fn new() -> Self {
return Something {
data: HashMap::new()
}
}
fn get(&self, c: char) -> Option<&u16> {
return self.data.get(&c);
}
fn add(&self, c: char, value: u16) -> Self {
let mut n = self.clone();
n.data.insert(c, value);
return n;
}
fn remove(&self, c: char) -> Self {
if !self.data.contains_key(&c) {
return *self; // <-- error[E0507]: cannot move out of `*self` which is behind a shared reference
}
let mut n = self.clone();
n.data.remove(&c);
return n;
}
}
fn main() {
let s = Something::new();
let s = s.add('a', 1);
let s = s.add('b', 2);
let s = s.remove('a');
// Should return the same instance as c is missing
let s = s.remove('c');
}
но если я изменю функцию remove
на возврат &Self
, я не смогу вернуть ссылку на переменную, объявленную в функции:
fn remove(&self, c: char) -> &Self {
if !self.data.contains_key(&c) {
return self;
}
let mut n = self.clone();
n.data.remove(&c);
return &n; // <--- error[E0515]: cannot return reference to local variable `n`
}
Я знаю, что могу избежать займов something
, но я не хочу передавать право собственности:
// <--- self and not &self
fn remove(self, c: char) -> Self {
if !self.data.contains_key(&c) {
return self;
}
let mut n = self.clone();
n.data.remove(&c);
return n;
}
Потому что тогда следующее не удастся:
fn main() {
let s = Something::new();
let s = s.add('a', 1);
let s = s.add('b', 2);
let s1 = s.remove('a');
let s2 = s.remove('a'); // <-- error[E0382]: use of moved value: `s`
}
Разве я не могу реализовать это сам?
Метод &self
->Self
всегда должен клонировать. Это фундаментальная вещь о собственности.
Есть два возможных решения этой проблемы, помимо очевидного «просто клонировать»:
Сделайте это &self
->&self
. Конечно, тогда вы не сможете его модифицировать, но Корова может это решить:
use std::borrow::Cow;
fn remove(&self, c: char) -> Cow<'_, Self> {
if !self.data.contains_key(&c) {
return Cow::Borrowed(self);
}
let mut n = self.clone();
n.data.remove(&c);
Cow::Owned(n)
}
Используйте дешевую клонируемую структуру данных, которая также допускает копирование при записи, например Rc или Arc:
use std::rc::Rc;
use std::collections::HashMap;
#[derive(Clone)]
struct Something {
data: Rc<HashMap<char, u16>>,
}
impl Something {
fn new() -> Self {
Something {
data: Rc::new(HashMap::new()),
}
}
fn get(&self, c: char) -> Option<&u16> {
self.data.get(&c)
}
fn add(&self, c: char, value: u16) -> Self {
let mut this = self.clone();
let n = Rc::make_mut(&mut this.data);
n.insert(c, value);
this
}
fn remove(&self, c: char) -> Self {
let mut this = self.clone();
if !this.data.contains_key(&c) {
return this;
}
let n = Rc::make_mut(&mut this.data);
n.remove(&c);
this
}
}
Можно также использовать обычный Cow
или вместо возврата «я или новое я» вернуть Some
в случае новой версии и None
в противном случае и позволить вызывающему абоненту разобраться с этим.
Существует встроенный тип копирования при записи (Корова), который должен подойти для вашего случая.