Early return after false condition

If often find myself writing code similar to this:

fn foo(...) -> Option<Foo> {
  if condition1 { return None; }
  if condition2 { return None; }
  if condition3 { return None; }
  let x1 = something1?;
  let x2 = something2?;
  let x3 = something3?;
  Some(combineit(x1, x2, x3))
}

The ? operator is convenient here as it allows to shorten these repetitive blocks:

if something.is_none() { return None; }
let x = something.unwrap();

But what about the condition checks?

if condition1 { return None; }

Is there a convenient way to shorten and shortcut them for example with something like:

condition1?;

Here is a real-world example:

fn cylinder_toi_pnt(&self, ray: &Ray, toi: f32, max_toi: f32) -> Option<(f32, Point3<f32>)> {
    if toi <= 0.0 || max_toi <= toi {
        None
    } else {
        let p = ray.point_at(toi);
        if p.z < 0.0 || self.height < p.z {
            None
        } else {
            Some((toi, p))
        }
    }
}

Something like this would be shorter and easier to read imho:

fn cylinder_toi_pnt(&self, ray: &Ray, toi: f32, max_toi: f32) -> Option<(f32, Point3<f32>)> {
    (toi <= 0.0 || max_toi <= toi)?;
    let p = ray.point_at(toi);
    (p.z < 0.0 || self.height < p.z)?;
    Some((toi, p))
}

This is a little bit like allowing conversion between bool and Option<()>.

You can do this conversion with bool::then_some():

fn cylinder_toi_pnt(&self, ray: &Ray, toi: f32, max_toi: f32) -> Option<(f32, Point3<f32>)> {
    (toi <= 0.0 || max_toi <= toi).then_some(())?;
    let p = ray.point_at(toi);
    (p.z < 0.0 || self.height < p.z).then_some(())?;
    Some((toi, p))
}

However, that's not especially idiomatic Rust and I would recommend using if condition1 { return None; } instead, unless your conditions are helper functions that you can just make return Option<()> directly to serve this purpose.

7 Likes

I agree if condition1 { return None; } is good enough, but if you use early return to do "continue with the value only when it is valid", then Option::filter would be natural.
However, it sometimes looks like "writing anything in one-liner" style even if it is valid usage...

fn cylinder_toi_pnt(&self, ray: &Ray, toi: f32, max_toi: f32) -> Option<(f32, Point3<f32>)> {
    let toi = Some(toi).filter(|&toi| 0.0 < toi && toi < max_toi)?;
    let p = ray.point_at(toi).filter(|&p| 0.0 <= p.z && p.z <= self.height)?;
    Some((toi, p))
}

This looks a lot like the assert! macro. Maybe write your own assert-like macro where you abstract if condition { return None; }? I think this (more or less) looks like idiomatic Rust:

fn cylinder_toi_pnt(&self, ray: &Ray, toi: f32, max_toi: f32) -> Option<(f32, Point3<f32>)> {
    assert_or_none!(toi <= 0.0 || max_toi <= toi);
    let p = ray.point_at(toi);
    assert_or_none!(p.z < 0.0 || self.height < p.z);
    Some((toi, p))
}

assert_or_none could obviously be a function instead of a macro, like @kpreid suggested, but I like the orthogonality with std's assert!.

1 Like

The error_stack crate has ensure!(cond), which runs return Err { .. } when cond is false.. which doesn't apply directly but I think "ensure" is a nice name for that..

2 Likes

So there is no way to implement the trait Try for bool? At least that's what the compiler suggests.

It would be possible for core to implement it, but it still wouldn't help you here, since it would only work in a method returning bool, not one returning Option.

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.