Help with borrower

I am trying to make a CLI-like command emulator, but I get this error when editing:

error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:19:36
   |
19 |                 commands[index] = &value.to_string();
   |                                    ^^^^^^^^^^^^^^^^^- temporary value is freed at the end of this statement
   |                                    |
   |                                    creates a temporary value which is freed while still in use
...
22 |         if allowed_commands.contains(&commands[0].to_string()) {
   |                                       -------- borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

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

The code is as follows:

let mut varibles: HashMap<String, String> = HashMap::new();

    loop {
        let allowed_commands = vec!["help".to_string(), "quit".to_string(), "credits".to_string(), "set".to_string(), "settoken".to_string(), "ls".to_string(), "system".to_string(), "set".to_string(), "echo".to_string()];
        let command =  input("$> ");
        let token = fs::read_to_string("/workspaces/codespaces-blank/spacetraders/src/token.txt").unwrap();
        let mut commands: Vec<_> = command.split_whitespace().collect();
        
        for (&ref key, &ref value) in varibles.clone().iter() {
            println!("looking for {key}");
            if commands.contains(&format!("${key}").as_str()) {
                let index = commands.iter().position(|&r| r == key).unwrap();
                commands[index] = &value.to_string();
        }
        
        if allowed_commands.contains(&commands[0].to_string()) {
            match commands[0] {
                "credits" => println!("You have {} credits", get_credits(&token).await),
                "help" => println!("Available commands: help, quit, credits"),
                "quit" => break,
                "settoken" => fs::write("token.txt", commands[1]).unwrap(),
                "ls" => {
                    let limit = *commands.get(1).unwrap_or_else(|| &"20");
                    let page = *commands.get(2).unwrap_or_else(|| &"1");
                    let limit = match limit.parse::<u64>() {
                        Ok(num) => num,
                        Err(_) => 20,
                    };
                    let page = match page.parse::<u64>() {
                        Ok(num) => num,
                        Err(_) => 1,
                    };
                    println!("{:#}", list_systems(&token, limit, page).await);
                }
                "system" => println!("{}", get_system(&token, commands[1].to_string()).await),
                "set" => {
                    let name = *commands.get(1).unwrap_or_else(|| &"");
                    let value = *commands.get(2).unwrap_or_else(|| &"");
                    varibles.insert(name.to_string(), value.to_string());
                }
                "echo" => println!("{}", commands.get(1).unwrap_or_else(|| &"")),
                _ => (),
            }
        } else {
            println!("spacetraders: {}: command not found", commands[0]);
        }
    }
    }}

How could I rewrite or improve the code so it will compile?

Please include the complete output from cargo check so we can see where the error is.

Please also include at least the signature of the function body this is in, and the signature of input so we better know what types are involved.


While I'm here though, let me rewrite this whole loop; hopefully this excercise will lead you towards more idiomatic code.

        for i in varibles.clone().into_iter() {
            println!("looking for {}", &&format!("${}", &i.0).as_str());
            if commands.contains(&&format!("${}", &i.0).as_str()) {
                let index = commands.iter().position(|&r| r == i.0).unwrap();
                commands[index] = i.1;
            }
        }

No need to clone the HashMap, we can just non-destructively iterate instead.

        for (&key, &value) in varibles.iter() {
            // replaced `i.0` with `key` and `i.1` with `value`

&str can be displayed directly.

            println!("looking for {key}");

Ah, this next part is probably where you got the weird &&format!(...).as_str() concept. You want to prepend a '$' and search for equality. Let's just roll with it for now, slightly cleaned up.

            if commands.contains(&format!("${key}").as_str()) {
                let index = commands.iter().position(|&r| r == key).unwrap();
                commands[index] = value;

Alright, that's a little cleaned up, but... searching through commands linearly multiple times is pretty indirect and inefficient. And there's also a logic problem -- what if the same variable is used more than once? I think this does the same thing:

        let commands: Vec<_> = command.split_whitespace().map(|word| {
            // If it starts with '$', see if it's a variable we recognize by
            // looking up the word after the '$'.  If it exists, we use the value
            // of the variable.
            //
            // If it doesn't exist or if the word doesn't start with '$', we
            // leave the word unchanged.
            word.strip_prefix('$')
                .and_then(|variable_name| varibles.get(variable_name).copied())
                .unwrap_or(word)
        }).collect();

Try running Clippy on your code and it will help you clean it up some more.

1 Like

That's a lot of unnecessary references. I'd start by cleaning the code of all the &&format!("${}", &i.0).as_str() and similar roundabout constructs; that'd simplify the code quite a bit so it would be clearer what you are actually trying to do.

Also, you should include a self-contained example. There is a whole bunch of undeclared variables in the snippet, which makes it harder to reproduce the error in the first place.

1 Like

Preformed all recommendations, and edited the post as required.

You need to make your varibles HashMap work with owned values (Strings).

    let mut varibles: HashMap<String, String> = HashMap::new();
    // ...
                    varibles.insert(name.to_string(), value.to_string());

(I moved your command logic outside of the varibles iteration loop. I didn't notice this in your OP so I think it was an accidental change when you edited.)

1 Like

Converted HashMapt to an Owned type, now getting an error about temporary values and now I have to use a let binding. How would I use a let binding would work like this?

If you can tweak the playground I linked above to produce the error you're seeing, I'll take another look. (Use the Share button in the upper right to get a link to your updated playground.)

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d2e4299a248a15ba673239ae7318ef18

That is all of the code that I am using. It will not work properly on the playground due to something about the json crate, but I think you could use something like replit.com for running the code if you don't have a local environment setup already. Also if possible, I can share the GitHub codespace with you.

-                commands[index] = &value.to_string();
+                commands[index] = value;

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.