Reputation: 6338
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
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
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
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
Reputation: 3762
Rust has no concept of truthy or falsy values like Python, so you cannot use str
s 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