Yeah, but you don't actually want to be forced to make your fields public. In any case, at this point we are walking right into a whole field of research about how best to specify which APIs can be composed with what -- i.e., you might like to be able to say things like "the method foo
can be called in parallel with bar
", without having to say what state they affect.
One way to do this that I personally pursued as part of my PhD is to partition the state abstractly, so that you can say "the method foo
uses only the group of fields called "foo-fields", and bar
uses the group of fields called bar-fields
, and these are disjoint", without actually revealing your fields. But there have been many other approaches (and probably I was just reinventing someone else's prior work, at this point I don't recall too well the full catalog).
I've been happy that with Rust we've largely sidestepped this whole problem. I've usually found that if "abstract groups of fields" are needed, you can usually achieve the same effect by defining two fields whose types are structs with private fields:
foo: FooFields
(and putting the methodfoo
on there); and,bar: BarFields
(and putting the methodbar
on there
This sort of says the same thing but without needing complex language features. But it makes it annoying because you can't do self.foo()
you must do self.foo.foo()
. And sometimes these divisions are not so simple and clear (for example, maybe foo
and bar
are disjoint, but baz
is only disjoint from bar
but not from foo
, and so forth...).
I wonder if there is a way to overcome some of this annoyingness (e.g., by "mirroring" the methods of foo
from self
such that self.foo()
is syntactic sugar for self.foo.foo()
or something).
Similarly, if I have to do some complex contract, one thing I occasionally (though rarely) do in my own code is to define a mirror structure that borrows fields. So imagine I have:
struct TheOwner {
map: HashMap,
vec: Vec
}
and I have some algorithm that writes to map
but reads from vec
. It might be defined on a struct:
impl TheOwner {
fn algorithm(&mut self) {
(TheAlgorithm { map: &mut self.map, vec: &self.vec }).go();
}
}
struct TheAlgorithm<'algorithm> {
map: &'algorithm mut HashMap,
vec: &'algorithm Vec,
}
impl<'algorithm> TheAlgorithm<'algorithm> {
fn go(&mut self) { ... }
}
This is annoying to define, but very flexible, and achieves the same effect as the inout
, in
categorizations.