Короткий ответ на ваш вопрос не. Невозможно выполнить это способом, аналогичным подходу C#. К счастью, Rust обеспечивает лучший контроль над изменчивостью, чем C#.
Понимание того, как неизменяемость работает в Rust и как она отличается от таких языков, как C# и Java, важна.
Неизменность в C#
class Foo {
readonly Bar bar = new Bar();
uint lives;
}
Некоторые вещи, чтобы отметить:
- Неизменность определяется в полевых условиях.
- Невосприимчивость неглубокая. Например, хотя ссылка на
bar
неизменна, значения, которые bar
ссылаются, все еще изменяемы.
- Неизменность в C# легко сворачивается отражением. Есть edge cases, где он может быть искажен даже без отражения.
Неизменность в Русте
struct Foo {
bar: Bar,
lives: u32
}
Первое, что нужно отметить, что определение структуры ничего не говорит о неизменности своих полей. Это потому, что в ржавчине нет такой вещи, как изменчивость на уровне поля. Изменчивость в Русте определяется на связывания до значения:
// Declare an immutable binding to a Foo
let foo = Foo { bar: Bar::new(), lives: 10 };
// Attempting to mutate the value that foo points to is a compile error
foo.lives = 5; // compile error!
foo.bar.baz = 6; // Also a compile error, foo is deeply immutable
// We can redefine the binding to be mutable
let foo = mut foo; // foo is now mutable!
foo.lives = 5; // mutating foo here would be valid
foo.bar.baz = 6; // this is also valid, foo is deeply mutable
Как вы можете видеть, переменчивость в Русте проще и менее нюансы, чем в C#: Это привязка к значению, которое определяет, будет ли это изменчиво, и он либо глубоко изменчив, либо глубоко непреложен. *.
Со всем этим, давайте попробуем смоделировать вашу проблему в Rust.
Во-первых, мы определим черта с эквивалентным GetNumber()
способом:
trait Bar {
fn number(&self) -> u32;
}
Поскольку number()
принимает непреложный привязку к self
, любой тип, реализующий Bar
не сможет мутировать себя через вызов к number()
:
struct Foo {
number: u32,
oranges: u32
}
impl Bar for Foo {
fn number(&self) -> u32 {
self.number += 1; // Compile error. We have an immutable binding to self
self.number
}
}
как вы можете видеть, контролируя переменчивость в Русте, все о контроле, как определяются привязки.
Введем метода для нашего признака, который определяет изменяемое связывание с self
и обновить нашу реализацию на Foo
:
trait Bar {
fn number(&self) -> u32;
fn inc_oranges(&mut self);
}
impl Bar for Foo {
fn number(&self) -> u32 {
self.number
}
fn inc_oranges(&mut self) {
// We have a mutable reference to self. We can mutate any part of self:
self.oranges += 1;
self.number += 1; // We can *also* mutate number
}
}
Это где вы могли бы начать в пользу C# подхода: В C#, вы можете объявить number
как поле readonly, оставляя oranges
изменчивым, но в Rust, если признак объявляет изменяемое связывание с self
, любая часть self
может быть мутирована. К счастью, есть способ обойти это.
*интерьер мутабильности
Ржавчина обеспечивает способ мутировать значения, которые являются частью непреложным связывания с путем cell модуля. Короче говоря, эти типы допускают мутацию, сохраняя при этом гарантии, обеспечиваемые использованием неизменяемых привязок. Он делает это, перемещая то, что в противном случае было бы проверкой времени выполнения с компиляцией (нулевой стоимостью) на проверку времени выполнения.
Давайте соберем все вместе сейчас:
struct Foo {
number: u32,
orange: Cell(u32) // allow mutation via an immutable binding
}
trait Bar {
fn number(&self) -> u32;
fn inc_oranges(&self); // self is now an immutable binding
}
impl Bar for Foo {
fn number(&self) -> u32 {
self.number
}
fn inc_oranges(&self) {
// We can mutate oranges via cell functions even though self is immutable
let cur_oranges = self.oranges.get();
self.oranges.set(cur_oranges + 1);
self.number += 1; // This would be a compile error
}
}
Таким образом, мы можем эффективно достичь эквивалент вашей C, например # путем:
- Определение неизменяемых привязок
self
на impls
- Использование типов ячеек для обеспечения внутренней изменчивости на неизменяемых связываниях
При всем том, что было сказано, было бы не так уж идиоматично или исполнять, чтобы моделировать все ваши типы таким образом. Более важно знать, когда и где уместно допускать мутацию, а не микроуправление мутацией определенных полей через типы ячеек.
У меня нет полного ответа для вас, поэтому я просто хочу оставить комментарий. 1) Более близкая аналогия для признака в C# будет интерфейсом. Как вы подходите к решению этого с помощью интерфейса? 2) Можете ли вы объяснить на более высоком уровне, какую проблему вы пытаетесь решить? Почему реализация этой черты должна содержать кусок неизменяемых данных? Почему он должен быть неизменным? –
Похож на проблему XY. Что именно вы пытаетесь сделать? – coredump