I'm currently trying to write a little command line app in Rust and I've hit a wall with lifetimes.
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...
}
The problem is that I'm trying to pass username
to the default value method, which requires a str
with a lifetime of 'r
. I tried cloning but I can't figure how to tell it what the lifetime of the clone is going to be. I tried something along the following lines:
let cln = (&*username).clone::<'r>();
arg.default_value(username)
For some reason its now telling me that username
doesn't live long enough, even though it shouldn't matter since I cloned the data.
So my question is, how do I make this compile?
EDIT: I'd like to add that its important to me that the signature stays the same aside from the lifetime parameters. I don't mind doing expensive operations such as cloning to make this work.
String.Clone () method is used to get a reference to the given instance of String. In this tutorial, we will learn about the syntax of C# String.Clone () method, and learn how to use this method with the help of examples. String.Clone () returns a reference to this instance of String. The syntax of Clone () method is
Introduction to clone () in C#. Clone () in C# is a method of string that is used to return the exact copy of an object. It returns the instance of the string. The return is just the copy with a different view. This method is also useful if we want to clone an array. In the case of the array, it creates a copy of the array with the same number ...
The syntax of Clone () method is This method returns an value of type Object. We may have to typecast it to String. In this example, we will take a string str and clone it using String.Clone () method. In this example, we will take a string str with null value, and clone it using String.Clone () method.
The clone () method is used to clone those elements. We can also change the value of any element of that cloned array. In the output, first, it is displaying the given array and the cloned array. We can also change the value by passing its indexing position.
malbarbo has provided some good solutions, but I'd like to discuss some aspects of the non-working code.
Let's start at the function signature:
fn get_username_arg<'r>() -> Arg<'r, 'r> {
This says "for any lifetime that the caller of this function picks, I will return an Arg
that contains references that will last that long". That's a pretty difficult promise to uphold, as the caller could request something that meets the 'static
lifetime, a value that lasts longer than the call to main
! In fact, the only way that you could meet the obligation of "any lifetime" is to return something that is 'static
.
This is a very good sign that there is going to be a problem. See also Why can't I store a value and a reference to that value in the same struct?, which shows this case as a constructor. Many people jump to attempting to return the String
along with the &str
, so that answer might short-circuit that avenue as well. ^_^
username
doesn't live long enough, even though it shouldn't matter since I cloned the data.
username
has a very specific lifetime and it's finite. If you look at a piece of code, it's generally straight-forward to find out the lifetime of an object: it's the scope of the block that the variable lives in without moving. In your example, username
only lives during the block that's part of the match arm Ok(username) => { // }
. As soon as that block exits, the value is destroyed.
clone
in this case has a signature of<'s>clone() -> &'s str
if you remove elisions (and reifySelf
) according to my very limited understanding of Rust in general.
env::var
returns a Result<String, VarError>
, and you access the Ok
variant, making username
a String
. The String
implementation of clone
takes a &String
and returns a String
. I'm not sure where the -> &'s str
would come from.
So if I clone with
clone::<'r>()
it should force the lifetime...
This is a very common mistake. Check out Do Rust lifetimes influence the semantics of the compiled program? (and maybe Why are explicit lifetimes needed in Rust?) for some background information. You cannot change the lifetime of something other than by rewriting your code to make the referred-to value have a larger scope. The lifetime syntax reflects how long the variable lives, it does not control it. There's no (safe) way to "force" a lifetime.
(&*username).clone
has that signature I mean
If we dereference and re-reference a String
, we end up with a &str
. That &str
will have a lifetime that corresponds to how long the String
lives. This makes sense because the &str
is just pointing to the String
. When the String
is deallocated, the &str
would be pointing at memory that is no longer in a valid state.
Arg::default_value
takes a &str
as parameter, this means that the string is not stored in Arg
, it is stored somewhere else. So the &str
value must outlive the Arg
that keeps the reference. If you use a &str
obtained from a String
value created in get_username_arg
(that's the case for username
), the Arg
will outlive the &str
(will live outside the get_username_arg
while the &str
lives only in the Ok
block), so this generates a compiler error.
One option is to pass the default username as a parameter:
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));
}
Note that username
is declared before arg
, so username
outlives arg
.
I'd like to add that its important to me that the signature stays the same aside from the lifetime parameters. I don't mind doing expensive operations such as cloning to make this work.
You do not show the Params
definition, but it seems that it is just a "name space" for some functions. If this is the case, you can change these functions to receive &self
as parameter (I know this is changing the signature, but the logic for creating args will stay in Params
), and store username
in 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();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With