克隆字符串到特定生命周期

时间:2016-05-28 18:42:13

标签: rust ownership-semantics

我目前正在尝试在Rust中编写一个小命令行应用程序,而且我已经终生碰壁了。

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...
}

问题在于我尝试将username传递给默认值方法,这需要str,其生命周期为'r。我尝试克隆,但我无法弄清楚如何告诉它克隆的生命周期是什么。我尝试了以下几点:

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

由于某种原因,它现在告诉我username不能活得足够长,即使自克隆数据以来它也不重要。

所以我的问题是,如何进行编译?

编辑:我想补充一点,对我来说重要的是签名与生命周期参数保持一致。我不介意做一些昂贵的操作,例如克隆来完成这项工作。

3 个答案:

答案 0 :(得分:5)

malbarbo has provided some good solutions,但我想讨论非工作代码的某些方面。

让我们从函数签名开始:

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

这表示“对于任何生命周期,此函数的调用者选择,我将返回包含将持续那么久的引用的Arg”。这是一个非常难以维护的承诺,因为调用者可以请求符合'static生命周期的内容,其值比调用main 更长!事实上,你能够履行“任何一生”义务的唯一方法就是返回'static的东西。

这是一个很好的迹象,表明会出现问题。另请参见Why can't I store a value and a reference to that value in the same struct?,它将此案例显示为构造函数。许多人跳过尝试将String&str一起返回,因此答案可能也会缩短该途径。 ^ _ ^

  

username的活动时间不够长,即使我克隆数据也没关系。

username具有非常特定的生命周期并且它是有限的。如果你看一段代码,通常可以直截了当地找出一个对象的生命周期:它是变量所在的块的范围而不移动。在您的示例中,username仅存在于匹配组Ok(username) => { // }的块中。一旦该块退出,该值就会被销毁。

  如果您根据我对Rust的一般理解非常有限而删除了精简(并且clone),则

<'s>clone() -> &'s str在这种情况下的签名为Self

env::var返回Result<String, VarError>,您可以访问Ok变体,username成为StringString implementation of clone需要&String并返回String。我不确定-> &'s str会来自哪里。

  

因此,如果我使用clone::<'r>()进行克隆,它应该强制终生......

这是非常常见的错误。查看Do Rust lifetimes influence the semantics of the compiled program?(也许Why are explicit lifetimes needed in Rust?)了解一些背景信息。您无法更改其他内容的生命周期,而不是通过重写代码来使引用的值具有更大的范围。生命周期语法反映了变量的存在时间,不控制它。没有(安全)方式“强迫”一生。

  

(&*username).clone我的意思是

如果我们取消引用并重新引用String,我们最终会得到&str&str的生命周期与String的生命周期相对应。这是有道理的,因为&str只是指向String。取消分配String后,&str将指向不再处于有效状态的内存。

答案 1 :(得分:3)

Arg::default_value&str为参数,这意味着该字符串未存储在Arg中,而是存储在其他位置。因此,&str值必须比保留引用的Arg更长。如果您使用从&str中创建的String值获得的get_username_argusername就是这种情况),则Arg将比&str更长} {将位于get_username_arg之外而&str仅存在于Ok块中),因此会产生编译错误。

一种选择是将默认用户名作为参数传递:

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));
}

请注意,username之前已声明arg,因此usernamearg更长。

  

我想补充一点,对我来说重要的是签名与生命周期参数保持一致。我不介意做一些昂贵的操作,比如克隆来使这项工作。

您没有显示Params定义,但它似乎只是某些功能的“名称空间”。如果是这种情况,您可以更改这些函数以接收&self作为参数(我知道这正在更改签名,但创建args的逻辑将保留在Params),并存储{{1在username中:

Params

答案 2 :(得分:0)

This answer解释了问题所在。

此处的解决方案是检索用户名(如果有),以便您将其作为String,然后您可以参考。

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),
} ;

工作示例:

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 :("),
    }
}