How to add an object to a hash map

I have a func creating an object with Command::new(). I want to add the result to a hashmap keyed by pid and I'm confused how to do it.

here is the toy code I'm playing with:

use std::collections::HashMap;
use std::io::{self};
use std::process::Command;
use std::process::Child;

fn execute(children: &mut HashMap<u32, Child>, arg: &str) {
    let child = Command::new("/usr/bin/echo")
        .arg(arg)
        .spawn();

    match child {
        Ok(child) => {
            // QUESTION: this appears to work, but should be &child to avoid copying?
            // doing merely that fails claiming the lifetime expires, so how do I handle child
            // above to make it work?
            children.insert(child.id(), child);
        }
        Err(err) => {
            eprintln!("failed to spawn: {}", err);
        }
    }
}

fn main() {
    let mut children: HashMap<u32, Child> = HashMap::new();

    let stdin = io::stdin();
    for line in stdin.lines() {
        let input = String::from(&line.unwrap());
        for word in input.split_whitespace() {
            execute(&mut children, word);
        }
    }

    dbg!(&children);
}

Converting hash type to HashMap<u32, &Child> and inserting &Child gives me:

error[E0597]: child does not live long enough
--> src/main.rs:15:41
|
6 | fn execute(children: &mut HashMap<u32, &Child>, arg: &str) {
| - let's call the lifetime of this reference '1
...
12 | Ok(child) => {
| ----- binding child declared here
...
15 | children.insert(child.id(), &child);
| ----------------------------^^^^^^-
| | |
| | borrowed value does not live long enough
| argument requires that child is borrowed for '1
16 | }
| - child dropped here while still borrowed

I want to store the actual object created within the func in the map, avoiding any copies (shallow or otherwise). If as is child is created on the stack and this causes the trouble, what's the way out?

So what's the expected way of handling this? Note the object being allocated within the func is a requirement for the actual code I intend to write.

While this question happens to include struct Child from std::process crate, I am interested in the general way of handling this.

In C I would have struct child *c = child_alloc(....); or similar and then would just assign the pointer.

You can't. Inserting a value inside a hashmap by definition means that the value has to exists inside the memory allocated by the map. The way you have it is the correct and recommended way. In Rust, this is what we mean when we say "moving" a value. Whether the generated code implements this with a copy is not specified.

You can insert references into a HashMap, but if you do, the target of those references needs to be created before the HashMap and live until after the HashMap dies. [ My terminology may not be great, but this is the way I understand it ].

Otherwise you have references pointing to something that no longer exists!

3 Likes

that much I figured, I'm asking how to do it in rust

Well, you could try moving

let child = Command::new("/usr/bin/echo")
        .arg(arg)
        .spawn();

to the top of your main function, before the HashMap is created, something like that. With such a small program it is a bit hard to see what it is aiming for.

In a case like this, it probably makes more sense for the HashMap to own the child object, but I am not sure why there is a HashMap here at all, I only glanced at your code.

this sounds rather suspicious. consider a more involved program where the object at hand is pointed to by some other object, storing a copy (even if shallow) instead of the actual object may be breaking something

note i'm a c programmer and very much new to rust, it may be the way to organize data is very different here and I'm just suffering from wrong assumptions

The rust compiler with its borrow checker will detect this and refuse to compile. It is the big featuer of Rust, so don't worry about it.

Storing by reference in Rust is done via Box, like Box<Child>. This type is a pointer.

OTOH the & syntax forbids storing the data. HashMap of &Child means that there are no Child objects stored there, and the type is forbidden from ever keeping any of them.

1 Like

The borrow checker checks this. You can't keep a reference to a moved value.

The whole point of Rust's “single ownership” and “move semantics” is that this doesn't happen. The bytes making up the value may be copied, as part of the implementation of a move, but when that happens, the original place they were is marked invalid — there is always exactly one copy of the value. (Unless the value is marked as freely copiable by the Copy trait.)

You're right to think about these questions, and the answers are “these hazards are prevented by the compiler” (or by data structures' code doing the same thing in more elaborate ways), as long as you're not writing unsafe code.

5 Likes

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.