Jeffrey04
Jeffrey04

Reputation: 6338

How can I return the first non-empty string?

The following Python code returns the first non-empty string (in this example, the content of bar):

foo = ""
bar = "hello"
foo or bar # returns "hello"

How do I write it in Rust? I tried with this:

let foo = "";
let bar = "";
foo || bar;

but I am getting this

error[E0308]: mismatched types
 --> src/main.rs:4:5
  |
4 |     foo || bar;
  |     ^^^ expected bool, found &str
  |
  = note: expected type `bool`
             found type `&str`

I suppose I can't easily do what I do in Python with Rust?

Upvotes: 3

Views: 2119

Answers (4)

Paolo Falabella
Paolo Falabella

Reputation: 25844

If you have several strings, I would use iterators;

let strs = ["", "foo", "bar"];
let non_empty = strs.iter().skip_while(|&x| x.is_empty()).next();

println!("{}", non_empty.unwrap_or(&""));

It can also go into its own function, if you're using this a lot:

// call as let non_empty = first_non_empty(&["", "foo", "bar"]);
fn first_non_empty<'a>(strs: &[&'a str]) -> &'a str {
    strs.iter().skip_while(|&x| x.is_empty()).next().unwrap_or(&"")
}

Upvotes: 4

Pan Hania
Pan Hania

Reputation: 478

You can also create an extension trait that will add your desired behavior:

trait Or: Sized {
    fn or(self, other: Self) -> Self;
}

impl<'a> Or for &'a str {
    fn or(self, other: &'a str) -> &'a str {
        if self.is_empty() { other } else { self }
    }
}

Now you can use it like this:

assert_eq!("foo".or("bar"), "foo");
assert_eq!("".or("").or("baz").or("").or("quux"), "baz");

If you need to make sure that the second argument is evaluated lazily you can extend the Or trait with the following method:

fn or_else<F: FnOnce() -> Self>(self, f: F) -> Self;

See analogous methods of Option: Option#or and Option#or_else for more details.

Upvotes: 5

Aratz
Aratz

Reputation: 440

The problem here is that Rust will not implicitly convert strings (or anything else) to booleans in logical expressions.

If you want to mimic Python's behavior, i.e. you want to keep strings after your boolean expression, you have to be more explicit with your code, e.g.:

if foo != "" {
    foo
} else if bar != "" {
    bar
} else {
    ""
}

Upvotes: 1

Jan Hohenheim
Jan Hohenheim

Reputation: 3762

Rust has no concept of truthy or falsy values like Python, so you cannot use strs as booleans. In fact, you cannot use anything but actual bools with comparison operators.

An alternative to @Aratz's solution using match would be

let result = match (foo.is_empty(), bar.is_empty) {
    (true,_) => Some(foo),
    (_, true) => Some(bar),
    _ => None,
};

If you want this kind or functionality for multiple strings, you can use a macro:

macro_rules! first_nonempty {
   ( $( $string:expr),+ )=> ({
        $(
            if !$string.is_empty() {
                Some($string)
            } else
        )+
        { None }
   })
}

Used like this

let res = first_nonempty!("foo", "bar", ""); // res is Some("foo")
let res = first_nonempty!("", "bar", "baz", "quux"); // res is Some("bar")
let res = first_nonempty!(""); // res is None

Upvotes: 4

Related Questions