0

I have a struct of arguments and then try to do some basic logic on the output and assign the argument value to a variable upon certain conditions:

struct Args {
    name: String,
    from: String,
}

fn main() {
    let args = Args {
        name: "Jane".to_string(),
        from: "Jane Doe <jane@example.com>".to_string(),
    };
    let default_username: String = "Sendmail Wrapper".to_owned();

    let username = if args.name != default_username {
        args.name.clone();
    };
    let username = if (args.name == default_username) && (args.from != default_username) {
        args.from.clone();
    };
    println!("{}",username);
}

When running the code above I get and error from Rust:

error[E0277]: `()` doesn't implement `std::fmt::Display`
  --> src/main.rs:73:19
   |
73 |     println!("{}",username);
   |                   ^^^^^^^^ `()` cannot be formatted with the default formatter
   |
   = help: the trait `std::fmt::Display` is not implemented for `()`
   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
   = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

If I use println!("{:?}",username); or println!("{:#?}",username); I simply get () printed.

How do I assign the struct variables to the username variable?

cafce25
  • 15,907
  • 4
  • 25
  • 31
  • This isn't related to `clap` at all, as you could condense this to the following [mre]: `println!("{}", if true { ""; })` – cafce25 Jul 29 '23 at 17:54

1 Answers1

0

You probably expected username to be of type String, but the problem lies in this:

let username = if args.name != default_username {
    args.name.clone();
};

Similarly, you then have this block, which essentially suffers from the same problem:

let username = if (args.name == default_username) && (args.from != default_username) {
    args.from.clone();
};

The compiler needs something more deterministic. In other words, you need to have and else block as well. Something like this:

let username = if <condition> {
    args.name.clone()
} else {
    args.from.clone()
}

Note that in that variant we are not only having an else block, which covers all possible cases, but the blocks don't have a ; at the end.


Why is it important not to have semicolons? Let's find out with this simple program:

fn main() {
    let t = 5;
    let username = if t > 6 {
        "foo";
    } else {
        "bar";
    };
    username.asdf();
}

And let's take a look at the compiler error:

error[E0599]: no method named `asdf` found for unit type `()` in the current scope
 --> src/main.rs:9:18
  |
9 |         username.asdf();
  |                  ^^^^ method not found in `()`

Now let's try the same thing without the semicolons:

fn main() {
    let t = 5;
    let username = if t > 6 {
        "foo"
    } else {
        "bar"
    };
    username.asdf();
}

The compiler error is slightly different this time:

error[E0599]: no method named `asdf` found for reference `&str` in the current scope
 --> src/main.rs:9:18
  |
9 |         username.asdf();
  |                  ^^^^ method not found in `&str`

As we can see, in the second example the compiler says username is of type &str (what we would have expected, right?). However, in the previous example (with the semicolons), the compiler says username is of type () (a tuple with zero elements, which in Rust is something like the void type in languages like C, C++, Java, C#, etc.).

The reason for this behavior is that we are using an if/else block as an expression and in Rust, when the last line in a block has no ; at the end, that essentially means return the_expression;.

Again, let me explain the above with an example. When you have this block:

{
    foo
}

Rust interprets that as:

{
    return foo;
}

But when you have this block:

{
    foo;
}

Rust interprets it as something like this:

{
    foo;
    return ();
}
at54321
  • 8,726
  • 26
  • 46
  • Thank you so much for your explanation! For future people who are confused like I was this additional reading helped me understand this answer to my question: https://stackoverflow.com/questions/43335193/why-does-an-if-without-an-else-always-result-in-as-the-value – fullinator Jul 29 '23 at 18:11
  • Nitpick - these `return`s are not to be taken literally, since taken literally they would mean "return from the current _function_" and not "...from the current _block_". That's only a single case when they are the same. – Cerberus Jul 31 '23 at 05:02