Suffer from a life time problem

there is a python function I want to translate it to rust:

    def artist_albums(self, artist_id, album_type=None, country=None, limit=20,
                      offset=0):
        params= dict()
        if album_type:
            params['album_type']=album_type
        if country:
            params['country']=country
        params['limit']=limit
        params['offset']=offset
        trid = self._get_id('artist', artist_id)
        return self._get('artists/' + trid + '/albums', params)

this is my rust version

    pub fn artist_albums(&self,
                         artist_id: &mut str,
                         album_type: Option<ALBUM_TYPE>,
                         country: Option<&str>,
                         limit: Option<u32>,
                         offset: Option<u32>)
                         -> Option<Albums> {
        let mut params: HashMap<&str, String> = HashMap::new();
        if let Some(_limit) = limit {
            params.insert("limit", _limit.to_string());
        }
        if let Some(_album_type) = album_type {
            params.insert("album_type", _album_type.as_str().to_owned());
        }
        if let Some(_offset) = offset {
            params.insert("offset", _offset.to_string());
        }
        if let Some(_country) = country {
            params.insert("country", _country.to_string());
        }
        let trid = self.get_id(TYPE::ARTIST, artist_id);
        let mut url = String::from("artists/");
        url.push_str(&trid);
        url.push_str("/albums");
        match self.get(&mut url, None, &mut params) {
            Some(result) => {
                // let mut albums: Albums = ;
                match serde_json::from_str::<Albums>(&result) {
                    Ok(_albums) => Some(_albums),
                    Err(why) => {
                        eprintln!("convert albums from String to Albums failed {:?}", why);
                        None
                    }
                }
            }
            None => None,
        }
    }

But

if let Some(_country) = offset {
            params.insert("country", _country.to_string());
        }

are not equal to

        if country:
            params['country']=country

it seems

if let Some(_country) = offset {
            params.insert("country", _country.to_string());
        }//=> lifetime ends here, so params will act like it hasn't insert anything ?

This is the complete version, I am going to build the Spotify api wrapper in Rust, translated from Python version

Is there a compiler error you're seeing? It's not clear what issue you're experiencing or what exactly you need clarification on.

I'm assuming your lifetime problem is due to the fact that the params hashmap doesn't own its keys, although without the compiler's error message it's hard to be sure. The keys are borrowed strings (&str) and must outlive the params hashmap to prevent memory issues. If you use a hashmap which owns its strings HashMap<String, String> then params.insert("limit".to_string(), ...) will ensure you don't have any memory issues and things will compile again.

I know this tangential to your original question, but that function signature is begging to be refactored using the builder pattern. It's pretty uncommon to take an Option as an argument, although you tend to see it when people translate code literally from other languages which use default values.

All the keys I see being added are constants (i.e. &'static str) which is completely fine on its own. There should be no need to insert String keys based solely on the code snippet shown, but that's why it'll be helpful for @samrayleung to clarify the exact problem :slight_smile:.

1 Like

no compiler error, it works, but doesn't work as expected

I know the builder pattern, and I have used it in my project, but doesn't show here. this is my point of view that album_type,limit..etc are not a part of my structure, they are just a function parameter. Moreover, My structure has a lot functions (at least 40 functions), all the functions may have a different parameters and default values, for example:

def foo(limit=10):
      pass
def bar(limit=1000):
      pass

so I think it is not proper to use builder pattern in this case.

For default params in your case this pattern can be useful.

Try to construct the minimal case in the playpen which will demonstrate this unexpected behaviour, it's hard to guess what exact is your problem.

For example here you can see that everything works as expected, but this line let Some(_country) = offset looks like a copy-paste mistake, maybe you meant to use country instead of offset?

Sorry for my mistake, this is my minimal case.

It's certainly not a minimal example. It just a copy-pasted code, which does not even compiles in the playpen. Often if you'll try to narrow down your code to demonstrate encountered problem you will be able to find solution yourself.

Thanks all guys, I will try to solve it myself