10

Was writing some code in Rust trying to define a CLI, using the (otherwise pretty great) crate clap, and run into a rather critical issue. Methods of App accept an Into<&'help str>, and i've been unable to find a way to implement this trait.

Indeed from what i understand, it is absolutely unimplementable:

struct JustWorkDamnIt {
    string: String
}

impl From<JustWorkDamnIt> for &str {
    fn from(arg: JustWorkDamnIt) -> Self {
        return arg.string.as_str()
    }
}

...which yields:

error[E0515]: cannot return value referencing local data `arg.string`
  --> src/cmd/interactive.rs:25:16
   |
25 |         return arg.string.as_str()
   |                ----------^^^^^^^^^
   |                |
   |                returns a value referencing data owned by the current function
   |                `arg.string` is borrowed here

Interestingly enough, however, returning a literal compiles just fine (which i reckon is why clap doesn't mind using this trait). Presumably that's because the literal is compiled to some static region of memory and therefore isn't owned by the function:

impl From<JustWorkDamnIt> for &str {
    fn from(arg: JustWorkDamnIt) -> Self {
        return "literal"
    }
}

But, i mean, surely there's a way to implement this trait and return dynamic strings? Maybe some clever use of Box<> or something, idk. I believe i've tried all the things i could think of.

And if there isn't a way, then this seems like a pretty glaring flaw for Rust - traits which can be declared and used in function headers, but cannot be actually implemented meaningfully (there's not much of a point in returning a literal). If this turns out to be the case i'll create an issue on the rust-lang repository for this flow, but first i wanted to sanity-check my findings here.


UPD: Thanks for the comments, made me think more in-depth about this issue.

The problem has to do with lifetimes, it seems. I apologize for not showing the entire code, i thought the snippets i shared would describe the problem sufficiently, but in hindsight it does make sense that the context would be important with Rust's lifetimes at play.

I did find a "solution" for this particular instance of the problem. Since the code in question will only run once per executable start, i can just Box::leak the &'static str and call it a day. Still, i would like to figure out if there's a more general solution which could be used for often-created dynamic strings.

impl Cmd for InteractiveCmd {
    fn build_args_parser<'a, 'b>(&'b self, builder: App<'a>) -> App<'a> {
        // leak() works here but i'm curious if there's a better way
        let staticStr : &'static str = Box::leak(Box::from(format!("Interactive {} shell", APPNAME).as_str()));
        let rv = builder
            // pub fn about<S: Into<&'b str>>(mut self, about: S) -> Self
            .about(staticStr) 
            .version("0.1");
        return rv;
    }
}

fn main() {
    let interactive = InteractiveCmd::new();
    let mut app = App::new(APPNAME)
        .version(APPVER)
        .author(AUTHORS)
      .subcommand(interactive.build_args_parser(App::new("interactive")));
}

Currently i am faced with 2 points of confusion here:

  • Why is there no impl From<String> for &str? The compiler claims that one exists for impl From<String> for &mut str, and i'm not seeing the importance of mut here.
  • ...and if there is a good reason to not impl From<String> for &str, would it make sense to somehow discourage the usage of Into<&str> in the public APIs of libraries?

Or maybe the issue is with my build_args_parser function and it's just not an option to offload the work to it as far as Rust is concerned?

  • 1
    In short: `&str` is a borrowed string, it can exist only while your `JustWorkDamnIt` exists. But `From` implementations consume the input value. Not sure how do you mean to fix this "flaw" - by forbidding trait declarations with functions like `fn something(self) -> &T`? – Cerberus Aug 29 '20 at 04:31
  • 2
    By the way, converting from borrow to borrow works fine - https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=82b8cd4ccf4a4dd50f873fe056829035 – Cerberus Aug 29 '20 at 04:33
  • 1
    Does this answer your question? [Return local String as a slice (&str)](https://stackoverflow.com/questions/29428227/return-local-string-as-a-slice-str) – trent Aug 29 '20 at 04:40
  • 1
    The Q&A I linked explains why it doesn't make sense from a language perspective to be able to turn an owned value into a borrowed one. It doesn't solve your `clap` issue, which you haven't really shared, but my crystal ball is saying you should try *borrowing* that `String` instead of just passing the whole thing in. Something like `app.method(&my_string)`, maybe? (I have not used `clap`, just guessing) – trent Aug 29 '20 at 04:45
  • @Cerberus after thinking about it for some time, i now realize that the problem with my code is more fundamental and has to do with lifetimes. Borrow to borrow conversion example is very helpful but i now see that i don't have an `&'a` parameter to borrow from to begin with. I have updated my post with extra code – notsodistantworlds Aug 30 '20 at 16:42
  • @trentcl the post you linked is certainly helpful but i'm not seeing any good alternatives currently. I've added more code to my question, that should make it more clear what i'm trying to do – notsodistantworlds Aug 30 '20 at 16:45
  • I strongly suspect the answer to your question is just "Clap isn't designed to work this way." dianhenglau's answer shows how you can pass a `&String` to a function expecting `impl Into<&str>`, but if the question is how you can move an owned `String` into `App` and have it cleaned up at the end -- well, if `clap` doesn't support that use case, there's not a lot you can do about it. – trent Aug 30 '20 at 22:07

1 Answers1

4

Seems like you're trying to convert a String into a &'a str. You can do it like this:

use clap::App;

fn main() {
    let s = format!("Interactive {} shell", "Testing");
    let app = App::new("Testing")
        .about(&*s); // or `.about(s.as_str());` it's the same
}

Here's the signature of the function that we want to call:

pub fn about<S: Into<&'b str>>(self, about: S) -> Self

So the about parameter must implement trait Into<&'b str>. According to std::convert::Into, we know that &'b str does implement trait Into<&'b str>. So now we just need a way to convert String into &'b str. The std::string::String tell us that there are two ways: use either s.as_str() or &*s.

dianhenglau
  • 484
  • 5
  • 9