0

I have read those two similar questions here and here. But still can't figure out how to achieve what I want: initializing a String field in a struct with a String that belongs to a HashMap in the same structure. Here is my initial code:

use std::collections::HashMap;

struct S<'a> {
    a: HashMap<String,Vec<String>>,
    b: &'a String,
}

impl <'a> S<'a> {
    fn new() -> S<'a> {
        let mut v:Vec<String> = Vec::new();
        v.push("a".to_string());
        v.push("b".to_string());
        let mut h = HashMap::new();
        h.insert("toto".to_string(),v);
        let b = &h.get("toto").unwrap()[0];
        S {
            a: h,           // error[E0505]: cannot move out of `h` because it is borrowed
            b: b            // error[E0515]: cannot return value referencing local variable `h`
        }
    }
}
fn main() {
    let mut s = S::new();
    println!("s.b = {}", s.b);
}

I would like b to be initilaized with a String value that belong to the HashMap a. Since the above did not work, I tried to initialize b with an empty String, and immediatly change b's value after creating s:

use std::collections::HashMap;

struct S {
    a: HashMap<String,Vec<String>>,
    b: String,
}

impl S {
    fn new() -> S {
        let mut v:Vec<String> = Vec::new();
        v.push("a".to_string());
        v.push("b".to_string());
        let mut h = HashMap::new();
        h.insert("toto".to_string(),v);
        let b = "".to_string();
        S {
            a: h,
            b: b
        }
    }
}
fn main() {
    let mut s = S::new();
    s.b = s.a.get("toto").unwrap()[0];  //error[E0507]: cannot move out of index of `Vec<String>`
    println!("s.b = {}", s.b);
}

Since I cannot copy the value into s.b:

    error[E0507]: cannot move out of index of `Vec<String>`
      --> src/main.rs:24:15
       |
    24 |         s.b = s.a.get("toto").unwrap()[0];  //error[E0507]: cannot move out of index of `Vec<String>`
       |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^ move occurs because value has type `String`, which does not implement the `Copy` trait

For more information about this error, try `rustc --explain E0507`.

I thought that a reference could work:

use std::collections::HashMap;

struct S<'a> {
    a: HashMap<String,Vec<String>>,
    b: &'a String,
}

impl <'a> S<'a> {
    fn new() -> S<'a> {
        let mut v:Vec<String> = Vec::new();
        v.push("a".to_string());
        v.push("b".to_string());
        let mut h = HashMap::new();
        h.insert("toto".to_string(),v);
        let b = &"".to_string();
        S {
            a: h,
            b: b        // error[E0515]: cannot return value referencing temporary value
        }
    }
}
fn main() {
    let mut s = S::new();
    s.b = &s.a.get("toto").unwrap()[0];  
    println!("s.b = {}", s.b);
}

But of course, I run into the same issue of referencing a value that does not have a lifetime longer than the function. No clue how/if I can tell the compiler that my empty String should live as long as S.

There might be an obvious solution, but if so, I can't see it :-(

bruno
  • 31
  • 5

1 Answers1

1

There might be an obvious solution, but if so, I can't see it :-(

If your goal is to share the value between the map and the field, then no. Rust does not like self-referential data structures.

And a string is affine, you can't have both the structure and the nested map own the "same" string unless:

  • you relax the ownership using Rc or Arc
  • you clone() the string and both just have a copy of the same thing

There are also crates for string caching / interning, but I've no idea how convenient & efficient they are.

Masklinn
  • 34,759
  • 3
  • 38
  • 57
  • Indeed, I want my String to point to an element in the HashMap (it's actually the SLD2 Surface I'll need to display at any given point in time. I thought about clone(), but did not like the idea of "duplicating" a Surface that contains an image. I will explore the other suggestions, which I'm not familiar with. Thanks. – bruno Nov 23 '21 at 14:41