CLI, StructOp, Options<String> (and the type system?)

As part of my command-line line option parsing, I'm trying compute the default network interface of the node but allow the user to override that default via '--interface '. There's something I'm not understanding about how to get and set the string vlue of the interface option in StructOpt though.

Does anyone have insight as to where I'm off the rails here?

use default_net::interface::get_default_interface_name;
use structopt::StructOpt;

#[derive(Debug, StructOpt)]
#[structopt(name = "Etherferris", about = "a port of Etherape to Rust")]
struct Opt {
    #[structopt(short = "i", long = "interface", default_value = get_default_interface_name().as_deref_mut())]
    interface: &mut str,
}

fn main() {
    let opts = Opt::from_args();
    println!("{:?}", opts);
}

The error message is:

7    |     #[structopt(short = "i", long = "interface", default_value = get_default_interface_name().as_deref_mut())]
     |                                                  -------------   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `&str`, found enum `Option`
     |                                                  |
     |                                                  arguments to this function are incorrect

The default_value = ... attribute tells structopt that it should use a particular string when the --interface argument isn't provided. Your get_default_interface_name() function probably returns a Option<String>, so the naive way to fix this would be with get_default_interface_name().as_deref_mut().unwrap_or_default().

However, it looks like you've got a couple other issues that mean it still won't work after applying that fix.

The interface field has the type &mut str. This is problematic because,

  1. Whenever you store a reference in a struct, you need to add a lifetime attribute (i.e. struct Opt<'a> { interface: &'a mut str }) otherwise it's a compile error
  2. A reference (&) implies that you are referring to a string that lives elsewhere, but the function creating our Opt, Opt::from_args(), doesn't take any arguments so where does this string come from?
  3. A &mut str isn't very useful because strings are more than just a Vec<char>. Essentially every mutation you can do may result in the string changing length (e.g. the uppercase form of the German ß is typically SS), but because you've got a mutable reference to part of a string, there's no way to resize it without going out of bounds (if the string needs to grow) or leaving "holes" of uninitialized memory behind (if the string needs to shrink)

What you probably want to do is make the interface an owned String.

You are also going to run into the semantic issue that being unable to get the default interface name will mean interface gets silently set to "" (the empty string). That means you'll need to remember to manually check for the empty string before proceeding.

A much better way is something like this:

#[derive(Debug, StructOpt)]
#[structopt(name = "Etherferris", about = "a port of Etherape to Rust")]
struct Opt {
    #[structopt(short, long)]
    interface: String,
}

fn main() {
    let opts = Opt::from_args();

    let Some(interface) = opts.interface.or_else(|| {
        get_default_interface_name().ok()
            .map(|interface| interface.name)
    }) else {
        todo!("Handle the case where the user didn't specify an interface and we can't automatically detect it")
    };
}
3 Likes

Michael,

When I posted I figured I was probably a bit "off" in a number of ways and that seems to be the case. :wink: There's a bit for me to digest here but its a great exercise. Thanks so much for your help!

In case you weren't aware, the next version of structopt is clap v3 (then v4)

Both include migration guides which I recommend following.

I know some people have been slower to upgrade intentionally, including

  • Wanting things to settle down which they have started to do
  • Change in --help terminal styling in v4.

But also a lot of people are still not aware of these new releases so I thought I'd pop in.

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.