I'd like to give something to the user, so that he can change the state of the struct, but if I give mutable reference to struct, the user can also replace the whole struct, which I do not want to be possible. As an alternative, I should be able to know if that happened.
struct Bar {
a: i32,
}
struct Foo {
bar: Bar
}
impl Foo {
pub fn bar_mut(&mut self) -> &mut Bar {
&mut self.bar
}
}
fn main() {
let mut foo = Foo {
bar: Bar {a: 100}
};
foo.bar_mut().a = 200; // this should be allowed
*foo.bar_mut() = Bar {a: 300}; // but i'd like to forbid this
}
The possible solution I can see is this. But I'd like to come up with something more compact.
struct Bar {
a: i32,
}
impl Bar {
pub fn a(&self) -> i32 { self.a }
}
struct BarMutWrap<'a>(&'a mut Bar);
impl<'a> BarMutWrap<'a> {
pub fn set_a(&mut self, a: i32) { self.0.a = a }
}
impl<'a> std::ops::Deref for BarMutWrap<'a> {
type Target = Bar;
fn deref(&self) -> &Self::Target {
&self.0
}
}
struct Foo {
bar: Bar
}
impl Foo {
pub fn bar(&self) -> &Bar {
&self.bar
}
pub fn bar_mut(&mut self) -> BarMutWrap {
BarMutWrap(&mut self.bar)
}
}
fn main() {
let mut foo = Foo {
bar: Bar {a: 100}
};
dbg!(foo.bar_mut().a());
foo.bar_mut().set_a(200);
dbg!(foo.bar().a());
}
Without knowing why you want this, it’s hard to give concrete advice— This is one of those things that has multiple solutions with varying tradeoffs, and which one is best for you will depend on the application.
One way to do this is to make Bar non-constructible by user code, either by adding a private field or marking it #[non_exhaustive]. If users can get access to two &mut Bars at the same time, though, they can still mem::swap them.
One option is something like this:
pub struct Bar {
pub a: i32,
/// Unique identifier
id: u32
}
struct FooBarView<'a {
parent: &'a mut Foo,
id: u32
}
impl std::ops::Deref for FooBarView<'_> {
type Target = Bar;
fn deref(&self) -> &Self::Target {
&self.parent.bar
}
}
impl std::ops::DerefMut for FooBarView<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.parent.bar
}
}
impl Drop for FooBarView<'_> {
fn drop(&mut self) {
if self.id != self.parent.bar.id {
// Fixup foo due to replaced bar
}
}
}
struct Foo {
bar: Bar
}
impl Foo {
pub fn bar(&self) -> &Bar {
&self.bar
}
pub fn bar_mut(&mut self) -> FooBarView<'_> {
FooBarView { id: self.bar.id, parent: self )
}
}
The problem is if the field is not a primitive type and allows some state change, so I want my reactive system to catch these changes, but not consider the field itself changed.