1. 25
  1.  

  2. 6

    Short and sweet and pretty helpful. Thanks :)

    1. 2

      Glad you liked it!

    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. 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. 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.