5

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

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

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

4

3 回答 3

7

malbarbo 提供了一些很好的解决方案,但我想讨论非工作代码的某些方面。

让我们从函数签名开始:

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

这表示“对于此函数的调用者选择的任何生命周期,我将返回一个包含将持续那么长时间的引用的引用”。这是一个很难兑现的承诺,因为调用者可以请求满足生命周期的东西,这个值比调用! 事实上,你可以履行“任何一生”的义务的唯一方法就是归还.Arg'staticmain'static

这是一个非常好的迹象,表明将会出现问题。另请参阅为什么我不能在同一个结构中存储一个值和对该值的引用?, 将这种情况显示为构造函数。许多人跳到尝试将 与String一起返回&str,因此该答案也可能使该途径短路。^_^

username活得不够长,尽管自从我克隆了数据以来这并不重要。

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

clone在这种情况下,根据我对 Rust 的一般理解非常有限,<'s>clone() -> &'s str如果你删除了省略(和 reify ),则有一个签名。Self

env::var返回 a Result<String, VarError>,然后您访问Ok变体,制作usernamea String。的String实现clone接受 a&String并返回 a String。我不知道-> &'s str会从哪里来。

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

这是一个非常常见的错误。查看Rust 生命周期是否会影响编译程序的语义?(也许为什么 Rust 需要显式生命周期?)了解一些背景信息。除了重写代码以使引用的值具有更大的范围之外,您无法更改某些内容的生命周期。生命周期语法反映了变量的生命周期,它不控制它。没有(安全的)方法可以“强迫”一生。

(&*username).clone我的意思是有那个签名

如果我们取消引用并重新引用 a String,我们最终会得到 a &str。那&str将有一个寿命,对应于寿命的长短String。这是有道理的,因为&str只是指向String. 当String被释放时,&str将指向不再处于有效状态的内存。

于 2016-05-28T20:17:18.050 回答
3

Arg::default_value将 a&str作为参数,这意味着字符串不存储在 中Arg,它存储在其他地方。因此,该&str值必须比Arg保留引用的值长。如果您使用&str从中String创建的值中获得的 a get_username_arg(对于 来说就是这种情况username),Arg则将比(将在仅存在于块中的情况下存在于&str外部),因此这会产生编译器错误。get_username_arg&strOk

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

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,所以usernameoutlifes arg


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

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

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();
}
于 2016-05-28T20:08:41.693 回答
0

这个答案解释了问题所在。

这里的一个解决方案是检索用户名(如果有的话),这样您就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 :("),
    }
}
于 2016-05-28T20:06:22.887 回答