Getting &str from String and Optional<String>

I have a String unformatted and an Optional formatted stored in a struct. I want to get a &str to either the optional if it's some or the other String without copying anything. Here is the current code that works:

let line: &str = match &self.formatted {
	Some(formatted) => formatted,
	None => &self.unformatted,
};

This syntax seems overly verbose, but I haven't found another easier way to do it (e.g. with unwrap_or or similar). Any suggestions on how to turn this into a one-liner? Thanks!

let line = self.formatted.as_deref().unwrap_or(&self.unformatted);
3 Likes

It's a single match. It's just fine. Idiomatic Rust uses matches all the time.

Option::as_ref works too.

pub struct S {
    unformatted: String,
    formatted: Option<String>,
}

impl S {
    pub fn foo(&self) -> &str {
        self.formatted.as_ref().unwrap_or(&self.unformatted)
    }
}

(Playground)

The difference is that as_deref will return Option<&str> while as_ref returns Option<&String> at first (which later gets coerced into Option<&str> where the &String later gets coerced to &str).

as_deref would fail if the option stores some other type (e.g. other than a String) that is !Deref. On the other hand, as_ref might result in a &String when type inference doesn't enforce coercion.

I slightly lean towards Option::as_ref though and would let the coercion happen later.


On the other hand, Option::as_ref won't work here, for example:

pub struct S {
    unformatted: &'static str,
    formatted: Option<String>,
}

impl S {
    pub fn foo(&self) -> &str {
        //self.formatted.as_ref().unwrap_or(&self.unformatted) // fails to compile
        self.formatted.as_deref().unwrap_or(&self.unformatted)
    }
}

(Playground)

So maybe Option::as_deref is better.

2 Likes

I have to say though, that @H2CO3 has a valid point:

It's at least arguable if

let line = self.formatted.as_deref().unwrap_or(&self.unformatted);

is really easier to read than:

let line: &str = match &self.formatted {
	Some(formatted) => formatted,
	None => &self.unformatted,
};

The former is short and concise but requires the reader to be more aware of the involved types. The latter may be more self-explaining to a reader who is skimming through the program.

I think both variants are fine.

1 Like

In this particular case, I think the combinator version's conciseness is valuable, but when longer and/or more complex computations are involved, I tend to begin to lean towards the match, because I don't want to hide the complexity too much.

In either case, I don't have a very strong opinion (other than match not being a big burden to read); for example, I see "always use combinators when you can for consistency" as an equally valid argument.

1 Like

And it doesn't need to be a match either. This is also possible, if you prefer it:

let line: &str =
    if let Some(formatted) = &self.formatted { formatted }
    else { &self.unformatted };

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=85633fe55977f6a72ea0ac837f56c8cf

2 Likes

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.