Feature Request: Enable `unwrap_or_else` to return same value as external function

I'd like this code to work:

use tracing::error;
use tracing::event;
use tracing::level_filters::LevelFilter;
use tracing::Level;
use tracing_subscriber::fmt;
use tracing_subscriber::prelude::*;
use tracing_subscriber::EnvFilter;

pub fn tracing_configuration() {
    tracing_subscriber::registry()
        .with(fmt::layer().with_file(true).with_line_number(true))
        .with(
            EnvFilter::builder()
                .with_default_directive(LevelFilter::INFO.into())
                .from_env()
                .unwrap_or(EnvFilter::from_default_env()),
        )
        .init();
    event!(Level::INFO, "Program has started.");
}

fn main() {
    tracing_configuration();
}

pub fn checked_u16_parse() -> Option<u16> {
    // I prefer this:
    let u_16 = "123".parse::<u16>().unwrap_or_else(|err| {
        error!("Failed to parse. Extra info: `{err}`.");
        return None;
    });

    // But I currently have to do this:
    let u_16 = match "123".parse::<u16>() {
        Ok(u_16) => u_16,
        Err(err) => {
            error!("Failed to parse. Extra info: `{err}`.");
            return None;
        }
    };

    Some(u_16)
}


This would simplify my code in many places, since this situation occurs frequently throughout the code when I want a program to never crash.

The general feature you're asking for is called "TCP-preserving closures" -- closures that can affect control flow in the containing function; TCP in this case stands for "Tennant's Correspondence Principle" -- and as I understand it, it's very unlikely to ever happen because it would have a lot of side effects on the rest of the language that nobody knows how to handle.

The specific case you are struggling with can be simplified by use of map_err and ok:

fn checked_u16_parse(s: &str) -> Option<u16> {
    s.parse::<u16>().map_err(|err| {
        eprintln!("Failed to parse. Extra info: `{err}`.");
    }).ok()
}

The map_err converts any Err(...) value into Err(()) after reporting the error, and then ok() turns the Result into an Option, which can be returned directly.

If you want to do more processing after the parse, you can use ? on Option as well as Result: for example

fn checked_u16_parse(s: &str, min: u16, max: u16) -> Option<u16> {
    let v = s.parse::<u16>().map_err(|err| {
        eprintln!("Failed to parse. Extra info: `{err}`.");
    }).ok()?;
    if !(min..=max).contains(&v) {
        eprintln!("Value {v} not within acceptable range [{min}, {max}]");
        return None;
    }
    Some(v)
}
11 Likes

More generally TCP preserving closures would be nice but here we have let-else too

6 Likes

Another solution would be postfix macros.

In your specific case you could also make an extension method that logs the error and then do .ok()? to early return.

5 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.