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?
1 Like
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 Read in std::io - Rust
Let me know if still unclear.
2 Likes
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.
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.
1 Like
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). 
Playground Code
That only happens if no trait with such a method exists.
3 Likes
Ah right, I forgot that it'll look for &T
impl and then auto-ref in this case.