Lifetime issues, creating & adding to hashmap structs with references

Hello, i don't understand why the lifetime of "lol" matters since it's cloned...

use std::collections::HashMap;
use std::sync::Arc;

struct St<'a>{
    asd: &'a str
}
trait Tr {}
impl Tr for St<'_> {}

struct Store<'a> {
    map: HashMap<u8,Arc<dyn Tr + 'a>>
}

impl<'x> Store<'x> {
    fn add<'method>(&mut self, lol: &'method str) -> Arc<dyn Tr + 'x> {
        let tmp: Arc<dyn Tr + 'x> = Arc::new(St{
            asd: lol.clone()
        });
        self.map.insert(1, Arc::clone(&tmp));
        tmp
    }
}

fn main() {
    println!("Hello, world!");
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/main.rs:17:22
   |
17 |             asd: lol.clone()
   |                      ^^^^^
   |
note: first, the lifetime cannot outlive the lifetime 'method as defined on the method body at 15:12...
  --> src/main.rs:15:12
   |
15 |     fn add<'method>(&mut self, lol: &'method str) -> Arc<dyn Tr + 'x> {
   |            ^^^^^^^
   = note: ...so that the types are compatible:
           expected &&str
              found &&'method str
note: but, the lifetime must be valid for the lifetime 'x as defined on the impl at 14:6...
  --> src/main.rs:14:6
   |
14 | impl<'x> Store<'x> {
   |      ^^
   = note: ...so that the expression is assignable:
           expected std::sync::Arc<(dyn Tr + 'x)>
              found std::sync::Arc<dyn Tr>

error: aborting due to previous error

error: could not compile `playground`.

To learn more, run the command again with --verbose.

Use the lifetime x instead:

impl<'x> Store<'x> {
    fn add(&mut self, lol: &'x str) -> Arc<dyn Tr + 'x> {
        let tmp: Arc<dyn Tr + 'x> = Arc::new(St{
            asd: lol
        });
        self.map.insert(1, Arc::clone(&tmp));
        tmp
    }
}

playground

If you want Store to own the string, use an owned String.

Shouldn't i own the cloned object?

It's not possible to own a &str. The clone just copied the reference, and you got a copy with the same lifetime. For example, this:

fn test(a: &str) -> String {
    a.clone()
}

fails to compile with

error[E0308]: mismatched types
 --> src/lib.rs:2:5
  |
1 | fn test(a: &str) -> String {
  |                     ------ expected `std::string::String` because of return type
2 |     a.clone()
  |     ^^^^^^^^^
  |     |
  |     expected struct `std::string::String`, found &str
  |     help: try using a conversion method: `a.to_string()`
  |
  = note: expected type `std::string::String`
             found type `&str`

playground

It is possible to deep copy a str, or any structure with references?

Sure, but the type will be different. If you need an owned string, use a String. There's also the Cow type, which could allow you to have a string that can be either an owned string, or a borrow of some other string.

1 Like

Fundamentally a &str is a reference to data stored somewhere else. Where do you expect the data you just copied to be stored? Rust is not garbage collected, so you need to tell it where to put it — the existence of a reference is not enough to keep it alive.

2 Likes

I would expected to be tied to struct St and be dropped when Arc counter reach zero.
Unfortunately i cannot change the type since in the ""real code"" where the issue originated, it's not a simple &str but a structure<'a> from an external crate..

Yeah well unfortunately this:

struct St<'a> {
    asd: &'a str
}

has no field that allows you to store the data of a string. The reference requires the data to be somewhere else. In fact St has no destructor, because it owns no data that needs cleaning up.

Can you link the type in the external crate?

I'm using flatbuffer, i've copied the relevant pieces

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Table<'a> {
    pub buf: &'a [u8],
    pub loc: usize,
}

#[derive(Clone, Copy, Debug, PartialEq)]
struct ExternalStruct<'a>{
    table: &'a Table<'a>
}

#[derive(Clone, Copy, Debug)]
struct St<'a> {
    field: ExternalStruct<'a>
}

trait Tr {}
impl Tr for St<'_> {}

struct Store<'a> {
    map: HashMap<u8,Arc<dyn Tr + 'a>>
}

impl<'x> Store<'x> {
    fn add<'method>(&mut self, lol: &'method ExternalStruct) -> Arc<dyn Tr + 'x> {
        let tmp: Arc<dyn Tr + 'x> = Arc::new(St{
            field: lol.clone()
        });
        self.map.insert(1, Arc::clone(&tmp));
        tmp
    }
}

fn main() {
    println!("Hello, world!");
}

I mean you could try to keep the lifetime around for the lifetime of the Store. Alternatively you could just make a vector from the buffer using field.table.buf.to_vec()

Probably i'm too attached to abstraction, i didn't even think about copying the underlying object! :smiley:

Just remove the lifetime 'method and do this:

use std::sync::Arc;
use std::collections::HashMap;

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Table<'a> {
    pub buf: &'a [u8],
    pub loc: usize,
}

#[derive(Clone, Copy, Debug, PartialEq)]
struct ExternalStruct<'a>{
    table: &'a Table<'a>
}

#[derive(Clone, Copy, Debug)]
struct St<'a> {
    field: ExternalStruct<'a>
}

trait Tr {}
impl Tr for St<'_> {}

struct Store<'a> {
    map: HashMap<u8,Arc<dyn Tr + 'a>>
}

impl<'x> Store<'x> {
    fn add(&mut self, lol: &'x ExternalStruct) -> Arc<dyn Tr + 'x> {
        let tmp: Arc<dyn Tr + 'x> = Arc::new(St{
            field: lol.clone()
        });
        self.map.insert(1, Arc::clone(&tmp));
        tmp
    }
}

fn main() {
    println!("Hello, world!");
}

... which, I guess is what Alice was trying to get at in her first sentence