How to Create a Clap Struct without StuctOpts

Hello Rust Folks,

I have been attempting to create a command-line application with Clap and I am having trouble implementing the creation of the Matches() struct with the usage of a function.

Context: I am fully able to create and utilise the clap application within main(). However, for the sake of test-ability, and the desire for a clean code base, I would like to make some sort of abstraction layer so i can create the clap application within tests without repeating the same instantiation commands over and over again.

Specifically, I can make it work when I do the specified example format:

fn main(){
    let matches = App::new("example app").get_matches();
}

However, I really want to create something where I can simply call a function and return either the full app, or the matches.

fn make_app()->Matches{
    let matches = App::new("example app").get_matches();
}

fn main(){
    app = make_app()
}

Whenever I try the second variation, the compiler complains that the matches or app struct have temporary values that are dropped once the function is completed.

I know this has to do with lifetimes and the borrowing system. I've read the rust book chapter on this time and time again and I feel i understand what it's trying to say here. I understand that this is failing because matches is really a reference which dies at the end of the function.

But this leaves me with the question of how do I actually instantiate this outside of main? Is this where macros start coming in? Or is clap simply not designed to be instantiated outside of main and this means I need to start considering structopts to get the functionality i'm hoping for?

I can do structopts, but I just have the format built out nicely in YAML for the app and it feels like I should be able to abstract this part out without needing to rely on a completely different crate.

Thanks for your time and expertise.

Answer Found:

The problem I was running into was the implementation of the YAML API which could only use borrowed references, thus, making clean code implementations nearly impossible witout the usage of leak.

As such, i just need to rebuild the app using something else.

Thank you to all of the great responses I recieved and for following up even after the original question evolved.

Maybe you could try structopt::StructOpt - Rust from_clap ?

I think this would work if you restructure it like this. get_matches is just a call to get_matches_from(std::env::args_os) in the library.

use clap::{App,ArgMatches};

fn make_app<'a>(args: &'a mut std::env::ArgsOs)->ArgMatches<'a>{
    App::new("example app").get_matches_from(args)
}

fn main(){
    let app = make_app(&mut std::env::args_os());
}
1 Like

I would recommend returning an App<'static, 'static> instead, this will allow you to use App::get_matches_from to provide the args from a vec in your tests: clap::App - Rust

i tried doing this but it's still throwing the same reference issue:

fn create_app_from_yaml()->App<'static,'static>{
    let yaml = load_yaml!("file/path/string");
    App::from(yaml)
}

it throws a "contains reference that is owned by this functiuon" error.

Everything that you create and borrow inside function has a lifetime limited to this function, and can't leave it. Lifetime annotations don't affect what happens, they only describe what the code is doing anyway. And your description as App<'static, 'static> is incorrect, because it says that your temporary yaml variable inside the function lives forever (the lifetime in the return type is the same as lifetime of the App::from arg, which makes it apply to the &yaml borrow).

If you want to leak some memory ('static is more or less leaked memory), then you can do it like this:

fn create_app_from_yaml()->App<'static,'static>{
    let yaml = load_yaml!("file/path/string");
    App::from(Box::leak(Box::new(yaml)))
}

but the interface of App is meant to be used like this:

fn create_app_from_yaml(yaml: &'a Yaml)->App<'a,'a>{  
    App::from(yaml)
}

fn main() {
   let yaml = load_yaml!("file/path/string");
   let app = create_app_from_yaml(&yaml);
}
1 Like

Thank you!

After several hours, I was getting something like this. But this leaves the question of can I abstract the from_yaml call into its' own function?

My ultimate goal is something that makes testing these two functions easy in multiple cases (as well as creating a sense of clean code) so i'm wondering if I would be better off just defining the app from the creation function itself.

Unfortunately clap's yaml API only works with borrowed Yaml structs, meaning separating the Yaml loading and App creation into its own function requires some workaround, such as Box::leak. I didn't realise this when posting as I hadn't used it for myself...

1 Like

Okay,

This gave me the answer I was looking for, i would rather not mess around with leak logic, so it may be better to just build the app within my function.