4

I want to use the regex crate and capture numbers from a string.

let input = "abcd123efg";
let re = Regex::new(r"([0-9]+)").unwrap();
let cap = re.captures(e).unwrap().get(1).unwrap().as_str();
println!("{}", cap);

It worked if numbers exist in input, but if numbers don't exist in input I get the following error:

thread 'main' panicked at 'called `Option::unwrap()` on a `None` value'

I want my program continue if the regex doesn't match. How can I handle this error?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Saeed M.
  • 2,216
  • 4
  • 23
  • 47
  • Note: in real code, you should get used to using `expect("what was expected")` rather than `unwrap`, it makes unexpected issues more easily recognizable. – Matthieu M. Mar 31 '17 at 14:24

2 Answers2

11

You probably want to (re-)read the chapter on "Error Handling" in the Rust book. Error handling in Rust is mostly done via the types Result<T, E> and Option<T>, both representing an optional value of type T with Result<T, E> carrying additional information about the absence of the main value.

You are calling unwrap() on each Option or Result you encounter. unwrap() is a method saying: "if there is no value of type T, let the program explode (panic)". You only want to call unwrap() if an absence of a value is not expected and thus would be a bug! (NB: actually, the unwrap() in your second line is a perfectly reasonable use!)

But you use unwrap() incorrectly twice: on the result of captures() and on the result of get(1). Let's tackle captures() first; it returns an Option<_> and the docs say:

If no match is found, then None is returned.

In most cases, the input string not matching the regex is to be expected, thus we should deal with it. We could either just match the Option (the standard way to deal with those possible errors, see the Rust book chapter) or we could use Regex::is_match() before, to check if the string matches.

Next up: get(1). Again, the docs tell us:

Returns the match associated with the capture group at index i. If i does not correspond to a capture group, or if the capture group did not participate in the match, then None is returned.

But this time, we don't have to deal with that. Why? Our regex (([0-9]+)) is constant and we know that the capture group exists and encloses the whole regex. Thus we can rule out both possible situations that would lead to a None. This means we can unwrap(), because we don't expect the absence of a value.

The resulting code could look like this:

let input = "abcd123efg";
let re = Regex::new(r"([0-9]+)").unwrap();
match re.captures(e) {
    Some(caps) => {
        let cap = caps.get(1).unwrap().as_str();
        println!("{}", cap);
    }
    None => {
        // The regex did not match. Deal with it here!
    }
}
Community
  • 1
  • 1
Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
  • 5
    Using `is_match` here is specifically recommended against: *"It is recommended to use this method if **all you need to do is test a match**"* (emphasis mine) — Using `is_match` combined with `captures` causes the regex to be evaluated twice, adding inefficiency. – Shepmaster Mar 31 '17 at 13:05
1

You can either check with is_match or just use the return type of captures(e) to check it (it's an Option<Captures<'t>>) instead of unwrapping it, by using a match (see this how to handle options).

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
hellow
  • 12,430
  • 7
  • 56
  • 79