Строка клонирования в определенное время жизни
В настоящее время я пытаюсь написать небольшое приложение для командной строки на 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 ответа
malbarbo предоставил несколько хороших решений, но я хотел бы обсудить некоторые аспекты нерабочего кода.
Давайте начнем с сигнатуры функции:
fn get_username_arg<'r>() -> Arg<'r, 'r> {
Это говорит: "Для любой жизни, которую выбирает вызывающая функция, я верну Arg
который содержит ссылки, которые будут длиться так долго ". Это довольно трудное обещание поддержать, так как вызывающая сторона может запросить что-то, что соответствует 'static
время жизни, значение, которое длится дольше, чем вызовmain
! Фактически, единственный способ выполнить обязательство "любой жизни" - это вернуть что-то, что 'static
,
Это очень хороший признак того, что возникнет проблема. См. Также Почему я не могу сохранить значение и ссылку на это значение в одной структуре?, который показывает этот случай как конструктор. Многие люди прыгают, пытаясь вернуть String
вместе с &str
, так что этот ответ также может привести к короткому замыканию. ^_^
username
не живет достаточно долго, хотя это не должно иметь значения, так как я клонировал данные.
username
имеет очень конкретное время жизни, и это конечно. Если вы посмотрите на фрагмент кода, обычно просто определить время жизни объекта: это область блока, в которой переменная живет без движения. В вашем примере username
живет только во время блока, который является частью спички Ok(username) => { // }
, Как только этот блок выходит, значение уничтожается.
clone
в этом случае имеет подпись<'s>clone() -> &'s str
если вы удалите решения (и reifySelf
) по моему очень ограниченному пониманию Rust в целом.
env::var
возвращает Result<String, VarError>
и вы получаете доступ к Ok
вариант, изготовление username
String
, String
реализацияclone
занимает &String
и возвращает String
, Я не уверен, где -> &'s str
придет из.
Так что, если я буду клонировать с
clone::<'r>()
это должно заставить всю жизнь...
Это очень распространенная ошибка. Проверьте, влияют ли времена жизни Rust на семантику скомпилированной программы? (и, возможно, почему в Rust нужны явные времена жизни?) для некоторой справочной информации. Вы не можете изменить время жизни чего-либо другого, кроме как переписав свой код, чтобы сделать указанное значение более широким. Синтаксис времени жизни отражает продолжительность жизни переменной, но не контролирует ее. Там нет (безопасный) способ "заставить" всю жизнь.
(&*username).clone
имеет эту подпись, я имею в виду
Если мы разыскиваем и повторно ссылаемся на String
мы заканчиваем с &str
, Тот &str
будет иметь срок службы, который соответствует как долго String
жизни. Это имеет смысл, потому что &str
просто указывает на String
, Когда String
освобождается, &str
будет указывать на память, которая больше не находится в допустимом состоянии.
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
в качестве параметра (я знаю, что это меняет подпись, но логика для создания аргументов останется в 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();
}
Этот ответ объясняет, в чем проблема.
Решением здесь было бы получить имя пользователя (если оно есть), чтобы оно было 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 :("),
}
}