1

I am trying to create a vector of trait objects but I'm getting a type mismatch. I also tried using .map() instead of a for loop but had the same problem. This is a minimal version of my real code, I realise this example doesn't require the use of trait objects but my real code does since there will be types other than Link in the List, which also implement Speak. How can I create a Vec with the correct type so I can pass it to trait_objects?

use std::fs;

fn main() {
    // let links = fs::read_dir("my_dir").unwrap();
    // let links: Vec<Box<dyn Speak>> = links
    //     .map(|file| {
    //         let myfile = file.unwrap();
    //         let href = myfile.path().to_str().unwrap();
    //         Box::new(Link { attr: href })
    //     })
    //     .collect();

    let links = Vec::new();
    for entry in fs::read_dir("my_dir").unwrap() {
        let myfile = entry.unwrap();
        let href = myfile.path().to_str().unwrap();
        links.push(Box::new(Link { attr: href }));
    }

    let link_objects = ListOfTraitObjects {
        trait_objects: links,
    };
    for link in link_objects.trait_objects.iter() {
        println!("{}", link.speak());
    }
}
trait Speak {
    fn speak(&self) -> String;
}

struct ListOfTraitObjects {
    trait_objects: Vec<Box<dyn Speak>>,
}

struct Link<'a> {
    attr: &'a str,
}
impl Speak for Link<'_> {
    fn speak(&self) -> String {
        self.attr.to_string()
    }
}
Max888
  • 3,089
  • 24
  • 55
  • Something like [this](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=866d8162a5e1f250c52abe37271bb306)? – Jmb Dec 30 '21 at 22:28

1 Answers1

2

There are several issues with your code. The first is that you're creating a Vec<Box<Link>> instead of a Vec<Box<Speak>>. By default Box::new (foo) creates a box of the same type as foo. If you want a box of some trait implemented by foo, you need to be explicit:

links.push(Box::new(Link { attr: href }) as Box::<dyn Speak>);

Playground

However this gives two new errors:

error[E0716]: temporary value dropped while borrowed
 --> src/lib.rs:7:20
  |
7 |         let href = myfile.path().to_str().unwrap();
  |                    ^^^^^^^^^^^^^                  - temporary value is freed at the end of this statement
  |                    |
  |                    creates a temporary which is freed while still in use
8 |         links.push(Box::new(Link { attr: href }) as Box::<dyn Speak>);
  |                    ----------------------------- cast requires that borrow lasts for `'static`

error[E0596]: cannot borrow `links` as mutable, as it is not declared as mutable
 --> src/lib.rs:8:9
  |
4 |     let links = Vec::new();
  |         ----- help: consider changing this to be mutable: `mut links`
...
8 |         links.push(Box::new(Link { attr: href }) as Box::<dyn Speak>);
  |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot borrow as mutable

The first error is because your Link struct borrows a string from entry, but entry only lives as long as the current loop iteration. Even though the error states that the cast requires a 'static lifetime, you would get a similar error if you were simply trying to build a Vec<Link> without casting (playground). You need to change the definition of your Link struct to contain an owned string:

struct Link {
    attr: String,
}
impl Speak for Link {
    fn speak(&self) -> String {
        self.attr.clone()
    }
}

The second error is easier and can be fixed simply by following the compiler suggestion and replacing let links = … with let mut links = ….

Full working example:

use std::fs;

fn main() {
    let mut links = Vec::new();
    for entry in fs::read_dir("my_dir").unwrap() {
        let myfile = entry.unwrap();
        let href = myfile.path().to_str().unwrap().to_string();
        links.push(Box::new(Link { attr: href }) as Box::<dyn Speak>);
    }

    let link_objects = ListOfTraitObjects {
        trait_objects: links,
    };
    for link in link_objects.trait_objects.iter() {
        println!("{}", link.speak());
    }
}
trait Speak {
    fn speak(&self) -> String;
}

struct ListOfTraitObjects {
    trait_objects: Vec<Box<dyn Speak>>,
}

struct Link {
    attr: String,
}
impl Speak for Link {
    fn speak(&self) -> String {
        self.attr.clone()
    }
}

Playground

The same fixes can be applied to your iterator-based solution:

let links = fs::read_dir("my_dir").unwrap();
let links: Vec<_> = links
    .map(|file| {
        let myfile = file.unwrap();
        let href = myfile.path().to_str().unwrap().to_string();
        Box::new(Link { attr: href }) as Box<dyn Speak>
    })
    .collect();

Playground

Jmb
  • 18,893
  • 2
  • 28
  • 55