Clone String to Specific Lifetime
Asked Answered
B

3

5

I'm currently trying to write a little command line app in Rust and I've hit a wall with lifetimes.

extern crate clap;
use self::clap::{App, Arg};
use std::env;

impl<'p> Params<'p> {
    fn get_username_arg<'r>() -> Arg<'r, 'r> {
        let mut arg = Arg::with_name("Username")
            .short("u")
            .long("username")
            .takes_value(true);
        match env::var("USERNAME") {
            Ok(username) => {
                // How do I pass `username` to default_value?
                arg.default_value(username)
            }
            Err(e) => arg.required(true),
        }
    }
    // More code below...
}

The problem is that I'm trying to pass username to the default value method, which requires a str with a lifetime of 'r. I tried cloning but I can't figure how to tell it what the lifetime of the clone is going to be. I tried something along the following lines:

let cln = (&*username).clone::<'r>();
arg.default_value(username)

For some reason its now telling me that username doesn't live long enough, even though it shouldn't matter since I cloned the data.

So my question is, how do I make this compile?

EDIT: I'd like to add that its important to me that the signature stays the same aside from the lifetime parameters. I don't mind doing expensive operations such as cloning to make this work.

Barthelemy answered 28/5, 2016 at 18:42 Comment(0)
P
8

malbarbo has provided some good solutions, but I'd like to discuss some aspects of the non-working code.

Let's start at the function signature:

fn get_username_arg<'r>() -> Arg<'r, 'r> {

This says "for any lifetime that the caller of this function picks, I will return an Arg that contains references that will last that long". That's a pretty difficult promise to uphold, as the caller could request something that meets the 'static lifetime, a value that lasts longer than the call to main! In fact, the only way that you could meet the obligation of "any lifetime" is to return something that is 'static.

This is a very good sign that there is going to be a problem. See also Why can't I store a value and a reference to that value in the same struct?, which shows this case as a constructor. Many people jump to attempting to return the String along with the &str, so that answer might short-circuit that avenue as well. ^_^

username doesn't live long enough, even though it shouldn't matter since I cloned the data.

username has a very specific lifetime and it's finite. If you look at a piece of code, it's generally straight-forward to find out the lifetime of an object: it's the scope of the block that the variable lives in without moving. In your example, username only lives during the block that's part of the match arm Ok(username) => { // }. As soon as that block exits, the value is destroyed.

clone in this case has a signature of <'s>clone() -> &'s str if you remove elisions (and reify Self) according to my very limited understanding of Rust in general.

env::var returns a Result<String, VarError>, and you access the Ok variant, making username a String. The String implementation of clone takes a &String and returns a String. I'm not sure where the -> &'s str would come from.

So if I clone with clone::<'r>() it should force the lifetime...

This is a very common mistake. Check out Do Rust lifetimes influence the semantics of the compiled program? (and maybe Why are explicit lifetimes needed in Rust?) for some background information. You cannot change the lifetime of something other than by rewriting your code to make the referred-to value have a larger scope. The lifetime syntax reflects how long the variable lives, it does not control it. There's no (safe) way to "force" a lifetime.

(&*username).clone has that signature I mean

If we dereference and re-reference a String, we end up with a &str. That &str will have a lifetime that corresponds to how long the String lives. This makes sense because the &str is just pointing to the String. When the String is deallocated, the &str would be pointing at memory that is no longer in a valid state.

Policlinic answered 28/5, 2016 at 20:17 Comment(3)
I am aware of that, which is why I'm cloning the value. clone in this case has a signature of <'s>clone() -> &'s str if you remove elisions(and reify Self) according to my very limited understanding of Rust in general. So if I clone with clone::<'r>() it should force the lifetime...Barthelemy
@JonathanBoudreau added more.Policlinic
(&*username).clone has that signature I mean.Barthelemy
N
3

Arg::default_value takes a &str as parameter, this means that the string is not stored in Arg, it is stored somewhere else. So the &str value must outlive the Arg that keeps the reference. If you use a &str obtained from a String value created in get_username_arg (that's the case for username), the Arg will outlive the &str (will live outside the get_username_arg while the &str lives only in the Ok block), so this generates a compiler error.

One option is to pass the default username as a parameter:

extern crate clap;
use self::clap::Arg;
use std::env;

pub struct Params;

impl Params {
    fn get_username_arg(default: Option<&str>) -> Arg {
        let arg = Arg::with_name("Username")
            .short("u")
            .long("username")
            .takes_value(true);
        if let Some(d) = default {
            arg.default_value(d)
        } else {
            arg.required(true)
        }
    }
}

fn main() {
    // type can be omitted
    let username: Option<String> = env::var("USERNAME").ok();
    // username.as_ref() produces Option<&String>
    // map(String::as_str) produces Some(&str) from Some(&String)
    // or None from None
    let arg = Params::get_username_arg(username.as_ref().map(String::as_str));
}

Note that username is declared before arg, so username outlives arg.


I'd like to add that its important to me that the signature stays the same aside from the lifetime parameters. I don't mind doing expensive operations such as cloning to make this work.

You do not show the Params definition, but it seems that it is just a "name space" for some functions. If this is the case, you can change these functions to receive &self as parameter (I know this is changing the signature, but the logic for creating args will stay in Params), and store username in Params:

extern crate clap;
use self::clap::Arg;
use std::env;

pub struct Params {
    username: Option<String>,
}

impl Params {
    fn new() -> Params {
        Params {
            username: env::var("USERNAME").ok(),
        }
    }

    fn get_username_arg(&self) -> Arg {
        let arg = Arg::with_name("Username")
            .short("u")
            .long("username")
            .takes_value(true);
        if let Some(d) = self.username.as_ref().map(String::as_str) {
            arg.default_value(d)
        } else {
            arg.required(true)
        }
    }
}

fn main() {
    let params = Params::new();
    let arg = params.get_username_arg();
}
Nonappearance answered 28/5, 2016 at 20:8 Comment(1)
My goal is to keep things organized in a way that all of the logic for the Arg creation stays in the get_username_arg function. Aside from the lifetimes I would prefer the signature stays the same.Barthelemy
F
0

This answer explains what the problem is.

A solution here would be to retrieve the user name (if any) so that you have it as a String you can then take a reference on.

let user_name = match env::var("USERNAME") {
    Ok(user_name) => Some( user_name ),
    Err(_) => None,
} ;
// Now we can take a reference on the user name String (if any) that can live
// long enough for the arg.
let arg = match user_name {
    Some(ref name) => arg.default_value(name),
    None => arg.required(true),
} ;

Working example:

extern crate clap ;

use std::env;
use clap::* ;

fn main() {
    let arg = Arg::with_name("Username")
        .help("The user name")
        .short("u")
        .long("username")
        .takes_value(true);

    let user_name = match env::var("USERNAME") {
        Ok(user_name) => Some(user_name),
        Err(_) => None,
    };

    let arg = match user_name {
        Some(ref name) => arg.default_value(name),
        None => arg.required(true),
    };

    let app = App::new("Test").arg(arg);

    let matches = app.get_matches();

    match matches.value_of("username") {
        Some(name) => println!("name: \"{}\"", name),
        None => println!("no name :("),
    }
}
Falls answered 28/5, 2016 at 20:6 Comment(1)
Right, I forgot to mention my solution is not wrapped in a function, it's just right where you construct the Arg and the App.Falls

© 2022 - 2024 — McMap. All rights reserved.