我目前正在尝试在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
不能活得足够长,即使自克隆数据以来它也不重要。
所以我的问题是,如何进行编译?
编辑:我想补充一点,对我来说重要的是签名与生命周期参数保持一致。我不介意做一些昂贵的操作,例如克隆来完成这项工作。
答案 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
成为String
。 String
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_arg
(username
就是这种情况),则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
,因此username
比arg
更长。
我想补充一点,对我来说重要的是签名与生命周期参数保持一致。我不介意做一些昂贵的操作,比如克隆来使这项工作。
您没有显示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 :("),
}
}