Optional first item of slice of references

Hello,

rather embarrasing beginner micro-confusion... i have frequent use case with:

  • input: a slice of references,
  • wanted output: Option with first reference for non-empty slice/array, or None if empty...

Is there some shortcut i may be missing? Following code feels a bit unnecessarily long.

Direct use of .first() instead of whole first_opt() leads to extra reference level (&&str) for Some() case...

fn first_opt<'a>(input: &'a [&'a str]) -> Option<&'a str> {
    if input.is_empty() {
        None
    } else {
        Some(*input.first().unwrap())
    }
}

fn main() {
    let some_strs = ["first", "second", "other"];
    assert_eq!(Some("first"), first_opt(&some_strs));

    let other_strs = [];
    assert_eq!(None, first_opt(&other_strs));
}

I would probably use input.first().map(|s| *s).

There's the as_deref method on Option as well, which I expected to work here, but it doesn't.

Note that you're duplicating lifetimes on first_opt, you probably don't want to put a lifetime annotation on the slice itself:

fn first_opt<'a>(input: &[&'a str]) -> Option<&'a str> {
3 Likes

right, i still sometime keep forgetting the "functional" helpers... :slight_smile: thanks for direction!

You can write that map as .copied().

I think it's nice with pattern matching too:

match *input {
    [first, ..] => Some(first),
    [] => None,
}
5 Likes

as_deref is akin to &*s, not *s. It will turn your String to a &str, but your &T stays a &T.

2 Likes

cuviper's input.first().copied() is a good answer to the original question here.

I wanted to address something that wasn't asked, since it's a common pitfall.

That general &'a &'a-like pattern is usually not what you want, since it ties more lifetimes together.

Specifically here it means that the lifetime you'll return is the shorter of the lifetime of the items and the lifetime of the slice. But there's no reason for the return value to care about the lifetime of the slice, since it has pulled out an item.

Here's a demo of this mattering:

fn first_opt<'a>(input: &'a [&'a str]) -> Option<&'a str> {
    input.first().copied()
}

fn demo() -> Option<&'static str> {
    let a = ["hello", "world"];
    first_opt(&a)
}

Compiling that, we get

error[E0515]: cannot return value referencing local variable `a`
 --> src/lib.rs:7:5
  |
7 |     first_opt(&a)
  |     ^^^^^^^^^^--^
  |     |         |
  |     |         `a` is borrowed here
  |     returns a value referencing data owned by the current function

Where the compiler is "correct" because of what we said in the first_opt signature, but that's not what we wanted to happen here -- those string literals are &'static str, so could be returned just fine.

If we change it to have a different lifetime for the slice,

fn first_opt<'slice, 'a>(input: &'slice [&'a str]) -> Option<&'a str> {
    input.first().copied()
}

then it compiles fine.

(That has a named lifetime for emphasis, but outside a pedagogical context I'd write it as just

fn first_opt<'a>(input: &[&'a str]) -> Option<&'a str> {
    input.first().copied()
}

naming only the lifetime that needs to be mentioned more than once to tie things together.)

4 Likes

Thanks all for subsequent discussion and details!

It is indeed true that lifetimes in my original case were bound together for my use case, and i defined it automatically from the beginning without thinking about it... It is true as well and great to point that it is redundant and restricting for potential further use...

Due to shortness of the final solution i ended removing the wrapper first_opt altogether, and just use .first().copied() to save extra use statement in all the files where needed...

1 Like

Even better :slight_smile: Always good to hear the orthogonal helpers are working out.

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.