Need help unnesting match tree

Hi there! I've got this monster of a function I'd like to make more readable:

fn read_bme280(
    tx: &Sender<String>,
    // previous: &Option<Measurements<I2cError>>,
    expected_temperature_step: f32,
    expected_humidity_step: f32,
    expected_pressure_step: f32,
) -> Result<Measurements<I2cError>, Option<(Measurements<I2cError>, f32, f32, f32)>> {
    trace!("read_bme280 start");
    match APP_STATE.bme280.lock() {
        Ok(mut bme280_lock) => match bme280_lock.get_mut() {
            Some(bme280) => match bme280.measure(&mut delay::FreeRtos) {
                Ok(measurements) => {
                    info!("{:?}", measurements);
                    match APP_STATE.bme280_last.read().as_ref() {
                        Ok(last_lock) => {
                            match last_lock.as_ref() {
                                Some(last) => {
                                    debug!("comparing to last measurements: {:?}", last);
                                    let suspect_temperature =
                                        (measurements.temperature - last.temperature).abs() > expected_temperature_step;
                                    let suspect_humdity =
                                        (measurements.humidity - last.humidity).abs() > expected_humidity_step;
                                    let suspect_pressure =
                                        (measurements.pressure - last.pressure).abs() > expected_pressure_step;
                                    if suspect_temperature || suspect_humdity || suspect_pressure {
                                        warn!("rejected: {:?} (reasons: temperature={suspect_temperature}, humidity={suspect_humdity}, pressure={suspect_pressure})", measurements);
                                        let next_temperature_step = expected_temperature_step
                                            + (expected_temperature_step
                                                * 0.1
                                                * suspect_temperature as u8 as f32);
                                        let next_humidity_step = expected_humidity_step
                                            + (expected_humidity_step * 0.1 * suspect_humdity as u8 as f32);
                                        let next_pressure_step = expected_pressure_step
                                            + (expected_pressure_step * 0.1 * suspect_pressure as u8 as f32);
                                        info!("temperature step: expected={expected_temperature_step}, observed={}, next={}", (measurements.temperature - last.temperature).abs(), next_temperature_step);
                                        info!(
                                            "humidity step expected={expected_humidity_step}, observed={}, next={}",
                                            (measurements.humidity - last.humidity).abs(),
                                            next_humidity_step
                                        );
                                        info!(
                                            "pressure step expected={expected_pressure_step}, observed={}, next={}",
                                            (measurements.pressure - last.pressure).abs(),
                                            next_pressure_step
                                        );
                                        return Err(Some((
                                            measurements,
                                            next_temperature_step,
                                            next_humidity_step,
                                            next_pressure_step,
                                        )));
                                    }
                                    debug!("no reason to reject, close enough to previous");
                                },
                                None => warn!("can't validate measurements, no previous measurements available for comparison!"),
                            }
                        },
                        Err(error) => {
                            error!("last measurements store poisoned! {error}");
                            return Err(None);
                        },
                    };
                    send_measurements(&measurements, tx);
                    debug!("measurements have been sent!");
                    Ok(measurements)
                }
                Err(error) => {
                    error!("reinit bme280 becaue of read failure: {:?}", error);
                    bme280
                        .init_with_config(&mut delay::FreeRtos, BME280_CONFIG.clone())
                        .ok();
                    Err(None)
                }
            },
            None => {
                error!("bme280 has not been initialized yet!");
                Err(None)
            }
        },
        Err(error) => {
            error!("couldn't aquire lock on bme280: {error}");
            Err(None)
        }
    }
}

This is my draft so far, I've not implemented the verification process the above function have, I think I'll put that in a seperate function that uses this one instead:

fn read_bme280_try() -> Result<Measurements<I2cError>, String> {
    let mut bme280_lock = APP_STATE
        .bme280
        .lock()
        .map_err(|error| format!("couldn't aquire lock on bme280: {error}"))?;
    let bme280 = bme280_lock
        .get_mut()
        .ok_or_else(|| "bme280 sensor is None")?;
    bme280.measure(&mut delay::FreeRtos).map_err(|error|
        bme280
            .init_with_config(&mut delay::FreeRtos, BME280_CONFIG.clone())
            .map_err(|init_error| {
                format!(
                    "bme280.measure failed: {:?}, reinit failed too: {:?}",
                    error, init_error
                )
            })
            .err()
            .or(Some(format!(
                "bme280.measure failed: {:?}, reinit succeeded.",
                error
            )))
            .unwrap()
    )
}

What I'm wondering now: Is there a way to unpack the Option with an .or(String) instead of the ugly .or(Some(String)).unwrap() that my new function has at the end?

    bme280.measure(&mut delay::FreeRtos).map_err(|error|
        bme280
            .init_with_config(&mut delay::FreeRtos, BME280_CONFIG.clone())
            .map_err(|init_error| {
                format!(
                    "bme280.measure failed: {:?}, reinit failed too: {:?}",
                    error, init_error
                )
            })
            .err()
            // changed
            .unwrap_or_else(|| format!(
                "bme280.measure failed: {:?}, reinit succeeded.",
                error
            ))
    )
2 Likes

Since you're doing a lot of work inside your error handling, maybe it is worth to factor it out into a separate function?

2 Likes

Unrelated question: Why are you running init_with_config inside a .map_err? I find it really weird that you initialize something inside of a function that is mapping an error...

1 Like

@quinedot thanks! that was exactly what I was looking for! :partying_face:

@moy2010 well after a failed read attempt I'm re-initializing the sensor in the hopes that the next attempt will work. Maybe that last block would look better as a match instead?

fn read_bme280_try() -> Result<Measurements<I2cError>, String> {
    let mut bme280_lock = APP_STATE
        .bme280
        .lock()
        .map_err(|error| format!("couldn't aquire lock on bme280: {error}"))?;
    let bme280 = bme280_lock
        .get_mut()
        .ok_or_else(|| "bme280 sensor is None")?;
    match bme280.measure(&mut delay::FreeRtos) {
        Ok(measurements) => Ok(measurements),
        Err(read_error) => Err(bme280
            .init_with_config(&mut delay::FreeRtos, BME280_CONFIG.clone())
            .map_err(|init_error| {
                format!(
                    "bme280.measure failed: {:?}, reinit failed too: {:?}",
                    read_error, init_error
                )
            })
            .err()
            .unwrap_or_else(|| {
                format!("bme280.measure failed: {:?}, reinit succeeded.", read_error)
            })),
    }
}

No, I think I prefer the initial version corrected with @quinedot's suggestion:

fn read_bme280_try() -> Result<Measurements<I2cError>, String> {
    let mut bme280_lock = APP_STATE
        .bme280
        .lock()
        .map_err(|error| format!("couldn't aquire lock on bme280: {error}"))?;
    let bme280 = bme280_lock
        .get_mut()
        .ok_or_else(|| "bme280 sensor is None")?;
    bme280.measure(&mut delay::FreeRtos).map_err(|read_error| {
        bme280
            .init_with_config(&mut delay::FreeRtos, BME280_CONFIG.clone())
            .map_err(|init_error| {
                format!(
                    "bme280.measure failed: {:?}, reinit failed too: {:?}",
                    read_error, init_error
                )
            })
            .err()
            .unwrap_or_else(|| {
                format!("bme280.measure failed: {:?}, reinit succeeded.", read_error)
            })
    })
}

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.