1. 25
  1.  

  2. 6

    Short and sweet and pretty helpful. Thanks :)

    1. 2

      Glad you liked it!

    2. 2

      Good article! I’m grappling with a similar problem lately. Coming from OO background, I tend to think about functions with a &self argument. However a fn with &mut self, Rust borrow checker doesn’t “see past” the fn. If I work directly with the fields of a struct, I can have multiple partial &mut borrows of different parts of the struct. It means I am forced to break encapsulation to make my code compile. Here’s an example of what I mean.

      struct Foo {
          p: u8,
          q: u8,
      }
      
      impl Foo {
          fn borrow_p(&mut self) -> &mut u8 {
              &mut self.p
          }
      }
      
      fn main() {
          let mut f = Foo { p: 1, q: 2 };
      
          let fp = &mut f.p; // works
          // let fp = f.borrow_p(); // doesn't work
          let fq = &mut f.q;
      
          *fp = 3;
          *fq = 4;
      }
      
      1. 1

        What’s the benefit of using a mutable reference to a field over just using the field directly?

        1. 1

          If the field doesn’t implement Copy, then “using the field directly” would mean moving it, which would mean either borrowing or moving the containing type.

          1. 1

            Then you could just mutably borrow, which is what you’d be doing by returning a mutable reference from a method. If you didn’t look at the context of my question, I’m asking what benefit f.borrow_p() would have over &mut f.p.

            1. 2

              Generally, if code is split out into a function, I’d expect it to be either for the purpose of reuse / encapsulation of some sequence of operations, or for stylistic / readability purposes. It may be frustrating then, when trying to reuse some code, or make your code appear more consistent with your personal taste or the styleguide of a project or team, to find that the code is rejected by the compiler because the function boundary obscures the disjointness of a borrow or partiality of a move.

              1. 2

                I think you might just be running into an artifact of a simplistic example meant to demonstrate the problem. The example doesn’t motivate the problem though, because the example has a very simple work-around.

                But it should be easy to see how this can become a problem in practice when things get a tiny bit more complicated. I documented one such example of it happening here (along with my work-around): https://github.com/BurntSushi/regex-automata/blob/6682f9cef5b4ef0c0892c4a6589658cd171106d8/src/nfa/compiler.rs#L22-L32

                cc @algesten - This is indeed one of my biggest complaints with Rust as a language. There is a palpable tension between the borrow checker and decomposition.

                1. 1

                  Thank you, this is the kind of answer I was hoping for.

                  1. 1

                    Yes. My problem is more complex than the example (multiple Vec where I want one element of each as it were).

                    I wonder if any of the future work on Polonius/NLL and forward will be able to “see through” at least the simplest of these cases.

                    1. 2

                      I don’t think so. Because that would imply the implementation of a function would leak into its signature somehow. The only things I’ve seen to address this are adding a way to explicitly annotate what a routine does and doesn’t borrow. But those are just brainstorming ideas. I don’t think there are any concrete plans to address this yet.

                      1. 1

                        Adding on, I think “borrow analysis won’t introspect into functions” has been a pretty solid position for the Rust project for years at this point.

                        1. 1

                          I’m thinking that NLL kinda magically put invisible lifetimes inside a scope that you only discover once you break them.

                          Partially borrowing a struct “through” the fn barrier could be a similar problem. It wouldn’t show in the signature, but be a compiler problem.

                          Like. The signature says &'a self, but in practice that could mean some part of self.

                2. 1

                  It’s a simplified example. What I have in practice is two Vec where I pick out specific items inside ones to be mutated.

                  1. 1

                    Gotcha, and if you pick out items from one of the Vecs inside a method and return a mutable reference it prevents you from doing anything with the other one? I can see why that could cause problems.

              2. 2

                Partial moves are fun! One thing to note is that methods which contain a partial move may not compile because the analysis doesn’t introspect beyond function boundaries. So even if the function body only uses, say, self.0, the overall function moves self, and so a partial move that would be acceptable without a function boundary becomes unacceptable with a function boundary in the presence of another partial move in the calling context.

                This same problem exists with disjoint borrows.

                1. 1

                  It took me a second to get what you meant by “Personally, I don’t see why Rust prevents moves like this, since it could easily reason that z is only partially defined in the same way that it already does for p”… it turns out there’s an issue that seems relevant: https://github.com/rust-lang/rust/issues/54987 it doesn’t touch on the half-owned bit, but it probably relates to why you can’t at this point accept the assignment to z even if only z.1 is touched later on. Interesting post.