【问题标题】:Clone String to Specific Lifetime将字符串克隆到特定生命周期
【发布时间】:2016-09-26 22:10:06
【问题描述】:

我目前正在尝试用 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 的寿命不够长,尽管自从我克隆了数据以来它应该不重要。

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

编辑:我想补充一点,除了生命周期参数之外,签名保持不变对我来说很重要。我不介意进行昂贵的操作,例如克隆来完成这项工作。

【问题讨论】:

    标签: rust ownership-semantics


    【解决方案1】:

    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&amp;str 一起返回,因此该答案也可能使该途径短路。 ^_^

    username 的寿命不够长,尽管我克隆了数据之后这应该不重要。

    username 有一个非常具体的生命周期,它是有限的。如果您查看一段代码,通常可以直接找出对象的生命周期:它是变量在不移动的情况下所在的块的范围。在您的示例中,username 仅存在于作为匹配臂 Ok(username) =&gt; { // } 一部分的块中。一旦该块退出,该值就会被销毁。

    在这种情况下,clone 的签名为 &lt;'s&gt;clone() -&gt; &amp;'s str,如果根据我对 Rust 的一般理解非常有限的理解(并具体化 Self)。

    env::var 返回一个Result&lt;String, VarError&gt;,然后您访问Ok 变体,使username 成为StringString implementation of clone 接受 &amp;String 并返回 String。我不确定-&gt; &amp;'s str 的来源。

    所以如果我用clone::&lt;'r&gt;() 克隆它应该强制生命周期......

    这是一个非常常见的错误。查看Do Rust lifetimes influence the semantics of the compiled program?(可能还有Why are explicit lifetimes needed in Rust?)了解一些背景信息。您不能更改某些东西的生命周期,除非重写代码以使引用的值具有更大的范围。生命周期语法反映了变量的生命周期,它不控制它。没有(安全的)方法可以“强制”一生。

    (&amp;*username).clone 有我的意思的签名

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

    【讨论】:

    • 我知道这一点,这就是我克隆值的原因。 clone 在这种情况下,如果根据我对 Rust 的非常有限的理解,删除省略号(并具体化 Self),则 &lt;'s&gt;clone() -&gt; &amp;'s str 的签名。所以如果我用clone::&lt;'r&gt;() 克隆它应该强制生命周期......
    • @JonathanBoudreau 添加了更多内容。
    • (&amp;*username).clone 有我的意思。
    【解决方案2】:

    Arg::default_value&amp;str 作为参数,这意味着字符串没有存储在Arg 中,它存储在其他地方。所以&amp;str 值必须比保留引用的Arg 寿命长。如果您使用从在get_username_arg 中创建的String 值获得的&amp;strusername 就是这种情况),则Arg 的寿命将超过&amp;str(将位于get_username_arg 之外,而@ 987654334@ 仅存在于 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));
    }
    

    注意usernamearg 之前声明,所以usernamearg 更有效。


    我想补充一点,除了生命周期参数之外,签名保持不变对我来说很重要。我不介意进行昂贵的操作,例如克隆来完成这项工作。

    您没有显示Params 定义,但它似乎只是某些函数的“名称空间”。如果是这种情况,您可以更改这些函数以接收 &amp;self 作为参数(我知道这是在更改签名,但创建 args 的逻辑将保留在 Params 中),并将 username 存储在 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();
    }
    

    【讨论】:

    • 我的目标是保持事物的组织方式,使 Arg 创建的所有逻辑都保留在 get_username_arg 函数中。除了生命周期,我更希望签名保持不变。
    【解决方案3】:

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

    【讨论】:

    • 对了,我忘了说我的解决方案没有封装在函数中,它正好在你构造ArgApp的地方。
    猜你喜欢
    • 2015-04-22
    • 1970-01-01
    • 2012-04-15
    • 2015-01-22
    • 1970-01-01
    • 1970-01-01
    • 2015-09-22
    • 1970-01-01
    • 2020-04-20
    相关资源
    最近更新 更多