Is it just another borrowing violation?


#1

Came over an error. The reason of which I failed to see.

extern crate bytes;

use bytes::*;

struct Outer {
    inner: Bytes,
}

fn test_borrowing(o: &Outer) {
    let buf=o.inner.into_buf(); /* this won't compile */
    //let buf=(&o.inner).into_buf(); /* but this does */
    if buf.iter().fold(true, |a, i| a && i==0) {
        println!("Hooray! Nothing in array!");
    }
}

fn main() {
    let o=Outer{inner: Bytes::from(vec![0, 0])};
    test_borrowing(&o);
}

It seemed to me it has roots in what implementation of IntoBuf is used to compile the both cases (uncommented and commented line). But the reason eludes. Can someone explain it?


#2

into_buf takes self so it’ll move/consume self. So in the non-working case, it tries to move inner but it’s borrowed; using the dot operator on a reference automatically derefs to the value. In the working case, you actually change the type to &Bytes and then call into_buf on that; the self becomes &Bytes (rather than Bytes in the non-working case) and since references are copy, it’s fine.

Another way to look at it is via UFCS. The non-working case is calling IntoBuf::into_buf(inner) whereas the working one is IntoBuf::into_buf(&inner).

This type of scenario is why various by_ref() adapters exist, such as https://doc.rust-lang.org/std/io/trait.Read.html#method.by_ref

Let me know if still unclear.


#3

It seems that IntoBuf is implemented for both Bytes and &Bytes:

impl IntoBuf for Bytes
impl<'a> IntoBuf for &'a Bytes

https://carllerche.github.io/bytes/bytes/trait.IntoBuf.html

I’ve recreated this scenario in a toy example in the playground.


#4

2 vitalyd:

Quite clear, thank you.

Another thing I may want to be sure of is the common way to write this line right. Do I have to specifically change the type the way I did, or is there a better line form?

2 MajorBreakfast:

Does it mean that the problem can come into scope explicitly because there are two implementations of IntoBuf, both for Bytes and &Bytes ?


#5

Yes. From playing around in the Rust playground this seems to be the case. I didn’t know that myself until just now. Quite interesting!


#6

The way you wrote it is fine. As mentioned, some places expose the by_ref() adapter so you could write outer.inner.by_ref().into_buf().

Yes; if Bytes didn’t have that impl, you’d get an error message about the method not being found.


#7

Okay, here’s a full analysis:

struct Outer {
    inner: Inner,
}

trait Foo {
    fn foo(self);
}

Here’s a table with all the scenarios.

  • Columns: Trait implementations: value, ref, both
  • Rows: Call styles
impl Foo for Inner impl<'a> Foo for &'a Inner impl Foo for Inner
impl<'a> Foo for &'a Inner
outer_ref.inner.foo() cannot move out of borrowed content (1) &inner cannot move out of borrowed content (2)
outer.inner.foo(); inner &inner inner
(&outer_ref.inner).foo()
(&outer.inner).foo()
cannot move out of borrowed content (3) &inner &inner

It seems that if multiple impls are available (col 3), it picks the impl according to how it’s called - by value (row 1 and 2) or by reference (row 3). The borrow checking then happens afterwards and it fails if it needs to move the value but can’t.

  • (1) Picks impl for Inner -> Error: Can’t move value
  • (2) Picks impl for Inner because it looks for Inner first because the struct field is a value, not a ref -> Error: Can’t move value
  • (3) Can’t find impl for &Inner -> Auto-deref: Pick impl for Inner -> Error: Can’t move value

Playground Code


So what happens if the struct stores a reference? TL;DR it looks for the &Inner impl first

struct Outer<'a> {
    inner: &'a Inner,
}
impl Foo for Inner impl<'a> Foo for &'a Inner impl Foo for Inner
impl<'a> Foo for &'a Inner
outer_ref.inner.foo() cannot move out of borrowed content &inner &inner
outer.inner.foo(); cannot move out of borrowed content &inner &inner
(&outer_ref.inner).foo()
(&outer.inner).foo()
cannot move out of borrowed content &inner &inner

First column is full of errors because it finds the Inner impl through auto-deref, but it can’t move the value because it’s always borrowed. If both impls exist (col 3), it picks &Inner. Note that the code in row 3 actually creates a reference to a reference which is a bit weird (but auto-deref gets rid of it). :slight_smile:

Playground Code


That only happens if no trait with such a method exists.


#8

Ah right, I forgot that it’ll look for &T impl and then auto-ref in this case.