Upcast to &mut dyn Trait

I want to keep a &mut impl Trait or &mut dyn Trait to a passed argument and the pass it along to other functions in my crate. Below is a stripped-down version of the code, to explain my situation.

extern crate byteorder; // 1.5.0

use core::fmt::Debug;
use std::io::{BufRead, BufReader, Cursor, Read, Seek, SeekFrom};
use std::error::Error;


pub trait BufReadExt : BufRead + Seek {
    fn read_string_at_offset(&mut self, offset: u64) -> Result<String, Box<dyn Error>>{
        let mut buf:Vec<u8> = Vec::new();
        self.seek(SeekFrom::Start(offset))?;
        self.read_until(b'\0', &mut buf)?;
        Ok(String::from_utf8(buf[..(buf.len()-1)].to_vec())?)
    }
}

impl Debug for dyn BufReadExt {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "BufReadExt{{}}")
    }
}

impl<T> BufReadExt for BufReader<T> where T: Read + Seek { }

impl<T> BufReadExt for Cursor<T> where T: AsRef<[u8]> { }


fn read_string(reader: &mut impl BufReadExt, offset: u64) -> Result<String, Box<dyn Error>>{
    reader.read_string_at_offset(offset)
}


#[derive(Debug)]
struct MyStruct {
    reader: mut dyn BufReadExt //How to I achieve this?
}


impl MyStruct {
    pub fn read_a_string_at_0(&self) -> Result<String, Box<dyn Error>> {
        read_string(&mut self.reader, 0)
    }
}

fn main() {
    let bytes = vec![0x41u8, 0x41, 0x41, 0x41, 0];
    let mut cursor = Cursor::new(&bytes);
    let res = read_string(&mut cursor, 0).unwrap();
    let mst = MyStruct{ reader: cursor };
    println!("{}", res);
    assert_eq!(res, "AAAA");
}


(Playground)

   Compiling playground v0.0.1 (/playground)
error: expected type, found keyword `mut`
  --> src/main.rs:35:13
   |
34 | struct MyStruct {
   |        -------- while parsing this struct
35 |     reader: mut dyn BufReadExt //How to I achieve this?
   |             ^^^ expected type

error[E0609]: no field `reader` on type `&MyStruct`
  --> src/main.rs:41:31
   |
41 |         read_string(&mut self.reader, 0)
   |                               ^^^^^^ unknown field

For more information about this error, try `rustc --explain E0609`.
error: could not compile `playground` (bin "playground") due to 2 previous errors

Is this achievable?

mut dyn Trait isn't a type. Did you want

  • &mut dyn BufReadExt
  • Box<dyn BufReadExt> or Box<dyn BufReadExt + '_> or similar
  • dyn BufReadExt, which would make your type unsized

Or perhaps you want

struct MyStruct<B> {
    reader: B
}

impl<B: BufReadExt> MyStruct<B> { .. }

Here's the last approach. I also made this change:

-    pub fn read_a_string_at_0(&self) -> Result<String, Box<dyn Error>> {
+    pub fn read_a_string_at_0(&mut self) -> Result<String, Box<dyn Error>> {

Because you can't reborrow a &mut _ through a &self.

1 Like

firstly, mut SomeType is not a type, if you remove mut, the struct definition will compile, but it's mostly useless because you cannot create a value of the struct. (however, you can transmute references to this struct, but that needs unsafe so it's delicate)

the "proper" way to use a dynamically sized type in this situation is make it generic and let the unsize coersion do its job.

from the code snippets, I guess you probably want a reference to a dyn BufReadExt, but don't know the syntax? if this is the case, to store a reference as a field, you just need to introduce a lifetime parameter to the type, like this:

#[derive(Debug)]
struct MyStruct<'b> {
    reader: &'b mut dyn BufReadExt
}

Good idea. I'll try the generics way. Thank you.

I tried using lifetime annotations but got stuck while creating object (in MyStruct::from_file( reader: &mut BuffReder<File>) -> Self method). It kept saying that reader "does not live long enough" because it is passed as &mut.

I wanted to keep MyStruct agnostic of the concrete of type of reader and refer only as the trait BufReadExt.

I also tried modifying the struct to consume the source as follow.

#[derive(Debug, Default)]
enum Source {
    File(BufReader<File>),
    Vec(Vec<u8>),
}

#[derive(Debug)]
struct MyStruct {
    source: Source
}

impl MyStruct{
    fn reader(&self) -> impl ReadBytesExt {
        match self.source {
            Source::File(f) => f,
            Source::Vec(v) => v,
        }
    }

But I could not figure out how to return same type from both match arms.

for this simple example, it is viable, as long you annotate the lifetimes correctly. however, for beginners, mut references are a slightly more difficult to deal with than shared references. before you get comfortable and good understanding with lifetimes, it'd be easier to avoid using references as struct fields, especially when dealing with mut references.

the natural and idiomatic way is just use generics, as @quinedot suggested. when impl an type with generic parameters, you are automatically restricted to only use the methods from the explicitly listed trait bounds.

the return position impl trait must be resolved to a single type, it's just an anonymous type that you didn't give it an explicit name. the two match arms in your code have two distinct types, that's why it doesn't compile.

to make it compile, you must consolidate the distinct types into one type, commonly used methods include:

  • erase the type for the return value, i.e. return trait object type, typically Box<dyn Trait> or &dyn Trait

  • create a "union" that can hold values from the distinct types, i.e. enum types

    • there exists librarys such as enum_dispatch etc. to help this kind of use cases
2 Likes

Thanks @nerditation, @quinedot .

I made some changes and now stuck with another error.

Rust Playground (rust-lang.org)

use std::io::{Seek, SeekFrom, BufRead, BufReader, Cursor};
use std::fs::File;
use std::error::Error;

type Result<T> = std::result::Result<T, Box<dyn Error>>;

trait BufReadExt: BufRead + Seek {
    #[allow(unused_variables)]
    fn read_str_at_offset(&mut self, offset: u64) -> crate::Result<String> {
        self.seek(SeekFrom::Start(offset))?; //requires `mut self`.
        Ok(String::from("test")) 
    }
}

impl BufReadExt for BufReader<File> { }
impl BufReadExt for Cursor<Box<[u8]>> { } //Can this be better?

trait Header {
    fn parse_buf(reader: &mut impl BufReadExt, pos: u64, offset: u64) -> crate::Result<Self> where Self: Sized;
}

#[derive(Debug, Default)]
struct HeaderField<T> {
    value: T,
    offset: u64,
}

struct TopHeader {
    field1: HeaderField<String>,
    field2: HeaderField<String>,
}

impl Header for TopHeader {
    fn parse_buf(reader: &mut impl BufReadExt, pos: u64, offset: u64) -> crate::Result<Self> {
        let s1 = reader.read_str_at_offset(offset)?;
        let s1len = s1.len() as u64;
        let s2 = reader.read_str_at_offset(offset + s1len)?;
        Ok( Self {
            field1: HeaderField {value: s1, offset: pos},
            field2: HeaderField { value: s2, offset: pos + s1len}
        } )
    }
}


struct Image {
    top: TopHeader,
    reader: Box<dyn BufReadExt>,
}

impl Image {
    fn parse_file(f: File, pos: u64) -> crate::Result<Self> {
        let mut reader = BufReader::new(f);
        reader.seek(SeekFrom::Start(pos))?;
        let top = TopHeader::parse_buf(&mut reader, 0, 0)?;
        Ok( Self {
            top,
            reader: Box::new(reader)
        })
    }

    fn parse_bytes(bytes: Box<[u8]>, pos: u64) -> crate::Result<Self> {
        let mut reader = Cursor::new(bytes);
        reader.seek(SeekFrom::Start(pos))?;
        let top = TopHeader::parse_buf(&mut reader, 0, 0)?;
        Ok( Self {
            top,
            reader: Box::new(reader)
        })
    }

    fn update_top(&mut self) -> crate::Result<()> {
        self.top = TopHeader::parse_buf(&mut self.reader, 0, 0)?; //Not sure how to pass reader here.
        Ok(())
    }
}

Error:

Compiling playground v0.0.1 (/playground)
error[E0277]: the trait bound `Box<(dyn BufReadExt + 'static)>: BufReadExt` is not satisfied
  --> src/lib.rs:73:41
   |
73 |         self.top = TopHeader::parse_buf(&mut self.reader, 0, 0)?; //Not sure how to pass reader here.
   |                    -------------------- ^^^^^^^^^^^^^^^^ the trait `BufReadExt` is not implemented for `Box<(dyn BufReadExt + 'static)>`
   |                    |
   |                    required by a bound introduced by this call
   |
   = help: the following other types implement trait `BufReadExt`:
             BufReader<File>
             std::io::Cursor<Box<[u8]>>
note: required by a bound in `Header::parse_buf`
  --> src/lib.rs:19:36
   |
19 |     fn parse_buf(reader: &mut impl BufReadExt, pos: u64, offset: u64) -> crate::Result<Self> where Self: Sized;
   |                                    ^^^^^^^^^^ required by this bound in `Header::parse_buf`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` (lib) due to 1 previous error

In essence, I need to accept a File or [u8]/&[u8]/Vec<u8> as a source,, create a reader as impl BufReadExt instance and pass the reader to downstream functions.

I always get stuck with lifetime of mutable references or references to moved values.

I did what the error message asked and added a third impl of BufReadExt to get it to compile:

        impl BufReadExt for Box<(dyn BufReadExt + 'static)> {}

The thing to remember here is that Box<dyn Trait> and &dyn Trait do not automatically implement Trait.

2 Likes

Thanks. It does work, but I would like to know why this is required.

Could any part of the code be updated so that I do not have seemingly redundant impl? I am OK to change function signatures or type of structs/members as long as the idea remains.

No, I'm afraid not. It is surprising that this impl isn't automatically done by the compiler. Just a few moments ago I added a link where @quinedot explains the details, but you probably didn't see it before you posted:

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.