Is it just another borrowing violation?


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])};

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?


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

Let me know if still unclear.


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

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

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


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 ?


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!


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.


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 cannot move out of borrowed content (1) &inner cannot move out of borrowed content (2); inner &inner inner
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 cannot move out of borrowed content &inner &inner; cannot move out of borrowed content &inner &inner
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.


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