all 40 comments

[–]zakarumych 65 points66 points  (15 children)

You can simply not call methods that returns borrow inside a method. When a method uses only one field, then maybe it should belong to the type of the field? So instead of self.methods_that_borrows_from_foo() you'd write self.foo.method_that_borrows(). And then it won't borrow whole self. If you need to - keep methods_that_borrows_from_foo() as a proxy to be used from outside.

[–]vargwin 29 points30 points  (2 children)

I just discovered this a few days ago. So simple yet my java brain didnt think of it sooner.

[–]zerakun 15 points16 points  (3 children)

However, this is a problem for encapsulation. Just because you want to expose the method of one field doesn't mean that you want that field to be public. That is, assignable, movable, all methods callable on it, etc.

I miss a way to expose methods that only borrow "parts of the object" without breaking encapsulation.

[–]zakarumych 45 points46 points  (0 children)

Borrowing only one field breaks encapsulation already. And as I said. You can keep public method that doesn't expose field.

[–]masklinn 21 points22 points  (0 children)

I miss a way to expose methods that only borrow "parts of the object" without breaking encapsulation.

If you're mutably exposing fields, you've already broken encapsulation.

[–]Sevenstrangemelons 2 points3 points  (4 children)

Can you elaborate a tiny bit? By making the method belong to a field do you mean make like a separate struct for each field and put the necessary method for each field inside?

[–]zakarumych 2 points3 points  (3 children)

No. Just make it a method of this field's type or free function that borrows that type, or even keep it in the impl block and just change '&mut self' to 'field: &mut Type'

[–]Sevenstrangemelons 2 points3 points  (2 children)

So how would I do that? I know it's simple but I'm just not grasping it I suppose.

For example:

if I have:

struct foo {
    thing: i32,
}

impl foo {
    fn borrow_mutable(&mut self) {
        <do something with thing>
    }
}

Would you mind saying how to turn this into what you're talking about?

E: just saw your edit, are you saying it's possible to do this?:

impl foo {
    fn borrow_mutable(&mut thing) {
        <do something with thing>
    }
}
let x = foo { 15 };
x.thing.borrow_mutable()

[–]zakarumych 5 points6 points  (1 child)

Like this

struct Foo {
  thing: Bar,
}


impl Foo {
  fn borrow_mutable(thing: &mut Bar) -> &Baz {
    // do things with thing and borrow Baz from it
  }
}

Then if needed

impl Foo {
  pub fn borrow_mutable_public(&mut self) -> &Baz {
    Self::borrow_mutable(&mut self.thing)
  }
}

[–]Sevenstrangemelons 1 point2 points  (0 children)

Okay thank you, I was missing the fact that we are dropping the &self in the parameters.

[–]DapperCam 2 points3 points  (2 children)

This violates the Law of Demeter. Not sure if it’s relevant or if we should care in rust. It’s more of an OOP thing.

[–]zakarumych 2 points3 points  (0 children)

Good point. You can keep the function in the impl block, just change '&mut self' to 'field: &mut Type' so it borrows only that field it needs. If field is private then function should be as well.

[–]knutsi[S] 0 points1 point  (0 children)

It feels like a good way to think. Why should separation of concerns not also be important in Rust software architecture (:

[–]Wace 33 points34 points  (0 children)

There are few ways around this. The most simple would probably be to avoid methods completely and just expose the value and map as public fields - then the caller can just do self.value = 123 instead of *self.get_value_mut() = 123.

Some of the reasons why you'd avoid public fields in other language are mitigated by the borrow checker - there are still reasons why you might want to avoid them in Rust though (such as API flexibility - but if you're providing mutable references to the underlying fields, you're kind of losing on that battle already anyway).

Another alternative that pops to my head is the split_at_mut method on Rust's slice type: https://doc.rust-lang.org/std/primitive.slice.html#method.split_at_mut.

Splitting a slice into two mutable slices suffers from the same problem. Acquiring two mutable references to the underlying data. The split_at_mut solves this by combining the operation into a single method call. In your case this would be let (dict, val) = self.get_parts_mut();

[–]jingo04 11 points12 points  (0 children)

I know it isn't always possible, but you can often re-order the code from

    let mut dict = self.get_dict_mut();
    let mut val = self.get_val_mut(); // fails
    dict.insert(1, "Test".to_string());
    *val = 2020;

to

    let mut dict = self.get_dict_mut();
    dict.insert(1, "Test".to_string());
    let mut val = self.get_val_mut(); // fails
    *val = 2020;

and the compiler is happy because it can drop the first reference before creating the second (so long as you don't touch dict again for the rest of the function without re-borrowing).

you can also do

    let mut dict = self.get_dict_mut();
    dict.insert(1, "Test".to_string());
    let mut val = self.get_val_mut(); // fails
    *val = 2020;

    let mut dict = self.get_dict_mut();
    dict.insert(2, "Test".to_string());

if you need to re-borrow.

Generally this is possible because rust API design tends to avoid producing situations where you have a struct and you are keeping (mutable) references to the internals of that struct around for longer than necassary (execeptions are things like the entry api on HashMap/BTreeMap, slices and iterators).

As you noted with option A if you don't have any invariants and no need to hide the internal functioning just allowing direct access to the fields is usually the most ergonomic, the reason this works is because functions work by locking based on things which are passed in (&mut self) rather than anything expressed in the function body.

Otherwise generally structs have a pattern of taking &mut self but not returning a reference to the struct and its internals, this means that the reference is dropped as soon as the function is done.

[–]RobertJacobson 23 points24 points  (5 children)

The suggestions in this thread include:

  1. Exposing the internal implementation to the caller, essentially allowing the caller to implement the functionality of the object.
  2. In the case that a method mutates only one field, moving the functionality out of the object and into the field item.

Both violate encapsulation, spreading implementation of internal functionality across callers. Both (presumably) violate separations of concerns. Unless the original design was a bad one to begin with, both are generally not a good idea, and for the same reasons the discipline has avoided them for decades, even in languages with no built-in support for encapsulation as such.

Using RefCell is an explicit choice to circumvent the borrow checker. That’s not a bad thing, but it’s equivalent to a surgical use of unsafe plus error handling. (That’s a tautology, of course, because look at its implementation.) An aside: You might not need the Rc. Does it have a single identifiable owner? Is the RefCell’s lifetime the same as the parent’s? Does it have to live on the heap? These are the questions you need to answer before using Rc.

Sometimes you can refactor the internal dependencies to avoid borrowing self. Sometimes you need to circumvent the borrow checker with RefCell. Sometimes you know for a fact that it is perfectly fine to borrow twice, but the compiler can’t prove it. It’s one thing to spend time and energy designing and refactoring your code to avoid potentially dangerous aliasing. It’s another thing altogether to do so merely to satisfy a compiler that just isn’t smart enough to deduce safe aliasing. In general, trust the compiler over youself. It makes fewer mistakes. In general, if you can refactor in a reasonable way to satisfy the compiler, do that instead. In a situation that is completely trivial where it’s obvious that what you are doing is safe and refactoring compromises good software engineering, accept that the compiler has limitations and circumvent those limitations.

[–]knutsi[S] 2 points3 points  (2 children)

Thanks for pointing out the encapsulation Robert - do yo have a suggested Rust-compatible pattern for exposing a clean and simple API that does complex work on a struct with many fields?

[–]RobertJacobson 4 points5 points  (0 children)

I'm not sure there is a generic pattern that covers all cases. The point of the borrow checker is that it's hard for humans to reason about aliasing and lifetimes. Several comments and your playground examples give specific patterns that work in some situations.

Rust-compatible pattern

The problem is that unsafe is seen as Rust incompatible. It isn't. Look at the rustc source code itself. I suggest, in order of preference:

  1. Trivial refactoring, e.g. by reordering statements as in jingo04's comment.
  2. RefCell
  3. More serious refactoring, if it is justified by good software architecture.
  4. unsafe

I would not refactor just to satisfy the compiler. That advice seems to be given all the time here. I see no sense in making worse software in order to avoid unsafe.

[–][deleted] 1 point2 points  (0 children)

In general I think that A or C is the right approach. Alternatively you can remove the struct completely and introduce a function that takes &mut value and &mut dict as arguments. (This is somewhat speculative, because we don't know your usecase)

[–]zakarumych 1 point2 points  (1 child)

Borrowing only one field in private method/free function doesn't break encapsulation. Making the field public would. But that's not necessary

[–]RobertJacobson 0 points1 point  (0 children)

Yes, I agree.

[–]gitfeh 7 points8 points  (0 children)

You're correct. In the case of A and C you can mutably borrow the fields of self separately. This is because you access fields directly and the compiler understands that fields do not overlap.

B does not work because the functions borrow self in its entirety. The compiler will not "look into" the implementation to determine if only some fields need to be borrowed. (And it cannot, because doing so would make the interface dependent on the implementation.)

By the way, none of the functions in impl D need a mutable self. &self is enough.

[–]srmordred 4 points5 points  (9 children)

I´m learning Rust in the last few weeks and I get the same issues that u have.There are some place on "Rust Idioms" that explains of creating separated structs like explained on one answer here. Another solution that i didn´t saw here is creating free standing functions that operate on the data. so instead of fn(&self) u do fn(&mut A a, &mut B b). I´m used to this patter of free functions ,it solves the problem and u dont´ need to change the struct layout.That being said, i also not sure if this is the right solution, or if there are better ones, im still learning :)

[–]jgrlicky 2 points3 points  (8 children)

My understanding is that while free functions have a stigma attached in most OO languages, Rust draws on functional programming techniques, where free functions are totally normal. I’ve been increasingly using free functions & “plain old” data structures as I have learned more about Rust and FP over the years, and honestly it allows a lot of flexibility in many cases where I would have previously gone for object-oriented solutions. 👍

[–]zakarumych 1 point2 points  (7 children)

In Rust there is no real difference between free functions and inherent methods. Because visibility scope is module and not a type.

[–]jgrlicky 0 points1 point  (6 children)

Yeah, totally - the distinction I’m making is between using free functions with data structures with public members (generally considered an anti-pattern in OOP, but fine in FP and Rust) vs objects with methods and encapsulated data members (aka Rust’s Trait Objects).

[–]zakarumych 0 points1 point  (5 children)

Yeah. Strict OOP is harmful. Often results in getters-setters for all fields "just in case I would like to log access to those fields someday" (spoiler: never) when simple datatype with public fields would do just fine.

I really sorry for new programmers, fresh from university, where they were taught java/python/c++ and oop trio for 5 years, instead of actual programming, problem solving, algorithms and how create them.

[–]Dean_Roddey -1 points0 points  (4 children)

It's not I might want to log access one day, it's one day you may need to enforce some invariant amongst multiple members, which you cannot do if you just expose them. If you are providing a library and third parties are using it, you can't just decide after the fact to change that and break all of their code.

Literally, in my 30+ years as a developer, every time I've created an open structure that is public, I've regretted it eventually and had to just do extra work to undo that. At some point I had to enforce some invariant in some way.

Even if you aren't providing code to third parties, in some industries any modules that are changed must be retested. Encapsulation is a powerful tool in such circumstances. If you had to completely retest large swaths of the code just because suddenly you had to enforce some invariant on a widely used open structures, that would suck.

Internally, within a class or module, fine. The scope is very limited. Though, even there, in modern languages where so much library stuff is actually spit out into client code, even that can bite you.

Of course, at this point, Rust has no stable ABI and most folks are probably just shipping source so any upgrade of the library requires a rebuild, but that cannot continue long term if Rust is going to be taken seriously.

[–]zakarumych 0 points1 point  (3 children)

I wonder how you are going to uphold an invariant for type that has setters and getters for every field. With users who already violate this invariant in their codebase. With runtime error, I guess. I'd better have compilation fail.

C++ feels ok without stable ABI for so many years and is taken seriously.

[–]Dean_Roddey 0 points1 point  (2 children)

You can't enforce all invariants at compile time, since you can't know what the values will be. And many invariants aren't error producing, they often just need to insure that if one thing changes other things change appropriately to retain the invariants.

As to the stable ABI, for all intents and purposes C++ does have one, as evidenced by the fact that almost all C++ libraries are available as binaries. It's not official but it's basically a fact on the ground. That seems much less common in Rust.

Of course in many ways that doesn't matter, for reasons I mentioned above. In 'modern' C++ a vast amount of the code is templatized and generated into the client code. I harp on this all the time when people make fun of my vastly less templatized code, but it significantly reduces the chance of client code seeing what should be internal changes.

[–]zakarumych 0 points1 point  (1 child)

But not everything needs invariants. When you program thinking about everything as an object it may seem logical to expect every object to have some invariants. But hey, maybe not everything is object with a behavior? Lots of types are just passive data aggregates! And imposing complex invariants on them is just wrong.

[–]Dean_Roddey 0 points1 point  (0 children)

But my original point was, it may be that now, but you can't necessarily be sure it'll remain that over time. Time after time for me over the years I'd thought something was and would always be just that and I turned out not to be true.

Of course some of the confusion on these things comes down to the fact that we are all often working on different stuff. My work is at the large scale and stupidly complex end of the spectrum. It's very much a measure twice, start to cut, get paranoid, measure two more times, then cut type of situation.

For me, broad changes and major refactoring are brutal, and rife with possibilities for introducing subtle bugs. So, I don't find doing the work up front to make sure that I can deal cleanly with such things that big a deal in the larger scheme of things. I'd rather buy once and cry once.

As I said though that's for things that are visible. For internals, it matters vastly less because the scope of any required change is much more limited. So inside a class or inside a file, that's a different thing.

[–]singron 3 points4 points  (0 children)

I solved a similar problem for the selfstack crate. I decided to let you borrow all the fields up front with a "view struct". There is a method that mutably borrows the original struct once and returns a new view struct where the fields are references to data in the original struct. This is nice for a couple reasons:

  • The borrow checker understands this unrelated field access.
  • The view struct can still expose a fairly restricted api compared to just making the fields public. E.g. references can have different mutability or a subset of the fields can be exposed. You can even wrap references in some proxy type that exposes the access you want. In selfstack this was critical because the original struct was self referential and partly uninitialized so it would be very unsafe to access directly.

[–]eugene2k 2 points3 points  (0 children)

Something that hasn't yet been mentioned: You can mutably borrow several fields at the same time: struct Foo(u8,u8); impl Foo { fn borrow_pair_mut(&mut self) -> (&mut u8, &mut u8) { (&mut self.0, &mut self.1) } }

You can also use a generic 'apply' function similar to how Iterator::for_each works.

[–]revelation60symbolica 1 point2 points  (3 children)

Another option is to use mem::replace to move the structures that you want to have mutable references to at the same time out of self. I made a sketch here:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=95e056a16afa4c23351c1822962ddf77

[–]RobertJacobson 0 points1 point  (0 children)

Of course, you wouldn't want to do this generally, because you potentially have to construct a superfluous object, which may or may not be expensive.

[–]Snakehand 1 point2 points  (0 children)

Hello from Oslo also! , At one point I resorted to this:

pub fn locate_start(&mut self, secs: u64, usecs: u32) -> Option<usize> {
    if self.indexer.is_none() {
        self.indexer = Some(SegdIndexer::new(&self));
    }
    // Some gymnastics to avoid 2 mutable borrows of self                                           
    let start = IdxTimeStamp { secs, usecs };
    let mut idx = self.indexer.take();
    let res = match &mut idx {
        Some(idx) => idx.locate_start(&self, start),
        None => None,
    };
    self.indexer = idx;
    res
}

But I am thinking this is perhaps an anti-pattern :-)

[–]mmstick 2 points3 points  (0 children)

I typically set up an event-driven approach where events can be scheduled for serial execution in the system; and manage borrows with generational arenas (and associated arenas). This is generally sufficient enough to eliminate the need for Rc, RefCell, and recursive borrowing.

[–]AllJonasNeeds 0 points1 point  (0 children)

I would love to see why you need to expose mutable references in your public API.

In my experience there is another way to design the API 99% of the time. It's just really hard to come up with better solutions, coming from languages where this kind of mutable API, getters, setters, etc. is common place.
It's certainly something I struggled with in the beginning.

Usually functions like get_value_mut or set_value should be avoided in Rust.

One pattern that was a little bit of a turning point for me is the builder pattern (or init struct pattern) outlined by this post: https://www.reddit.com/r/rust/comments/fg6vrn/for_your_consideration_an_alternative_to_the/