1

I'm having trouble understanding how references get forwarded through functions. The following scenario seems to compile as expected:

trait Trait {}

struct ImplementsTrait {}
impl Trait for ImplementsTrait {}

fn foo(t: &mut Trait) {
    // ... use the mutable reference
}

fn forward(t: &mut Trait) {
    foo(t); // forward the type '&mut Trait' to foo
}

fn main() {
    let mut t = ImplementsTrait{};
    forward(&mut t); // need to pass as reference because Trait has no static size
}

However, in using the API for the capnp crate, I get unexpected behavior:

fn parse_capnp(read: &mut BufRead) {
    let reader = serialize_packed::read_message(read, message::ReaderOptions::new());
    Ok(())
}

fn main() {
    // ... ///
    let mut br = BufReader::new(f);
    parse_capnp(&mut br);
    Ok(())
}
error[E0277]: the trait bound `std::io::BufRead: std::marker::Sized` is not satisfied
  --> src/main.rs:18:16
   |
18 |     let reader = serialize_packed::read_message(read, message::ReaderOptions::new());
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `std::io::BufRead` does not have a constant size known at compile-time

The signature of read_message is:

pub fn read_message<R>(
    read: &mut R, 
    options: ReaderOptions
) -> Result<Reader<OwnedSegments>> 
where
    R: BufRead,

It appears that read is getting passed by value when it is a &mut BufRead and read_message is expecting a &mut BufRead. The only way to get this snippet to compile for me is changing this to:

fn parse_capnp(mut read: &mut BufRead) {
    let reader = serialize_packed::read_message(&mut read, message::ReaderOptions::new());
    Ok(())
}

I believe I am missing something simple about the types here. To me, this appears to pass a &mut &mut BufRead, which is not the expected type, but compiles.

Could someone add clarity to the types of read and t for the two examples?

I've looked at the following threads:

For the first thread, I'd say the comparison to C-style pointers is faulty due to the dereferencing rules that Rust applies.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Mike Lui
  • 1,301
  • 10
  • 23
  • Why would you create a minimal example that *does* work, but not one that *doesn't*? – Shepmaster May 21 '18 at 17:51
  • 1
    That is exactly my confusion. I originally created that minimal example expecting it **not** to work, and was surprised when it did, since I am still hazy on Rust references. The original question was going to be why would I need `mut t: &mut Trait` since that seemed strange to me, but then realized that I **didn't** need to make the parameter mutable. So now I'm confused as to whether my minimal example isn't doing what I think it's doing, or whether there's some caveat for the specific API function I'm using. To me, `read_message` and `foo` look to be passed the same type. – Mike Lui May 21 '18 at 17:55

1 Answers1

4

Creating a Minimal, Complete, and Verifiable example that reproduces the problem is a useful step:

use std::io::BufRead;

pub fn read_message<R>(read: &mut R)
where
    R: BufRead,
{}

fn parse_capnp(read: &mut BufRead) {
    read_message(read);
}

fn main() {}
error[E0277]: the trait bound `std::io::BufRead: std::marker::Sized` is not satisfied
 --> src/main.rs:9:5
  |
9 |     read_message(read);
  |     ^^^^^^^^^^^^ `std::io::BufRead` does not have a constant size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `std::io::BufRead`
note: required by `read_message`
 --> src/main.rs:3:1
  |
3 | / pub fn read_message<R>(read: &mut R)
4 | | where
5 | |     R: BufRead,
6 | | {}
  | |__^

The error message is well covered in existing questions:

TL;DR: trait objects aren't guaranteed to have a size, but generics have a Sized trait bound by default.

read is getting passed by value

Yes, everything in Rust is always passed by value. Sometimes that value happens to be a reference though.

read_message is expecting a &mut BufRead

It is not. It is expecting a generic type that implements the trait BufRead. These two signatures are different:

// Reference to a concrete type
pub fn read_message<R>(read: &mut R)
where
    R: BufRead,
// Trait object
pub fn read_message<R>(read: &mut BufRead)

See also:

a &mut &mut BufRead, which is not the expected type

It's a perfectly cromulent type. BufRead is implemented for any mutable reference to any type that implements BufRead itself:

impl<'a, B: BufRead + ?Sized> BufRead for &'a mut B

Besides, in this case you don't have a &mut &mut BufRead, you have a &mut &mut R. The concrete monomorphization for the types you've shown is actually a &mut &mut Bufreader.


You can fix it by :

  1. changing the read_message function to accept unsized types. This is fine since R is always behind a pointer:

    pub fn read_message<R>(read: &mut R)
    where
        R: ?Sized + BufRead,
    
  2. changing the parse_capnp function to take a reference to a concrete type instead of a trait object:

    fn parse_capnp<R>(read: &mut R)
    where
        R: BufRead,
    {
        read_message(read);
    }
    
  3. changing the parse_capnp function to take a concrete type instead of a trait object. You then need to take a reference to it yourself:

    fn parse_capnp<R>(mut read: R)
    where
        R: BufRead,
    {
        read_message(&mut read);
    }
    
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    *These two signatures are different:* Aaaah, that was precisely my confusion. I assumed they were analogous and had difficulty narrowing down my misconceptions. Thank you. – Mike Lui May 21 '18 at 18:17
  • 1
    @MikeLui: You're not the first to get confused by that distinction - [a new `dyn Trait` syntax](https://rust-lang-nursery.github.io/edition-guide/2018/transitioning/traits/dyn-trait.html) is being added in the next version of Rust to make it clearer what is and isn't a trait object. Once Rust 2018 comes out later this year, this will be recommended over the bare trait object syntax (i.e. the compiler will tell you to change `&mut BufRead` to `&mut dyn BufRead`). – Joe Clay May 22 '18 at 12:37
  • I ended up passing a Box. I still find the fact that Box works fine and &BufRead causes issues to be strange, but that may just be because my head is still in C++ world. They both look like just base pointers to me. *shrugs* – Mike Lui May 24 '18 at 02:58
  • @MikeLui `Box` is indirection without need. Chances are that you won't ever see the performance hit, but it's the principle of the matter. [This is what I'd do](https://play.rust-lang.org/?gist=59eeadcfb37ece36d62301545d29d73a&version=stable&mode=debug). – Shepmaster May 24 '18 at 03:17
  • @Shepmaster Thank you for the example. I can see how that would be faster with static dispatch. In my case, a file can either be gzipped or raw bytes, so I don't know my `BufReader` type ahead of time (`File` or `GzDecoder`), and chose dynamic dispatch over generating two entire code paths depending on the type. – Mike Lui May 24 '18 at 04:03
  • @MikeLui ah yes, in the dynamic case it is a reasonable choice. You may be able to [avoid the heap allocation if you really wanted to though](https://stackoverflow.com/a/28220053/155423). – Shepmaster May 24 '18 at 04:07
  • @Shepmaster One last clarification to ask. I actually made a helper function to return a Box depending on the file extension. I don't think I can return a &BufRead trait object because the lifetimes of the stack allocated BufReaders would go out of scope. Do I have that right? – Mike Lui May 24 '18 at 17:01
  • @MikeLui that's correct! If you *reallllly* wanted to avoid it, [you could through some closures](https://play.rust-lang.org/?gist=201cb02a1dc7d05ededbe1c942f1e071&version=stable&mode=debug), but boxed trait objects are certainly a good tool to use for your case. – Shepmaster May 24 '18 at 17:16