[Feature Request] Implement a method for slice/str to get the only element/char if only have one

Consider implementing something behavior like the following code:

fn as_one_elem<T>(slice: &[T]) -> &T {
    let mut iter = slice.into_iter();
    let elem = iter.next().unwrap();
    matches!(iter.next(), None);
    elem
}

fn as_one_char(s: &str) -> char {
    let mut iter = s.chars();
    let elem = iter.next().unwrap();
    matches!(iter.next(), None);
    elem
}

The code above just shows the way I do this in general. If standard library includes such methods, then I can reduce them in business logic.

Two methods could be implemented: one returns a unique element/character directly and panic when there is not only one, and one that returns a Result. It is also can be considered implementing From/TryFrom, but I think that might not be proper.

I didn't check the standard library one method by one method, but searching based on keywords didn't search for what I needed. Tell me I'm a fool if there is already a more convenient way to do the same thing.

Regarding existing solutions:

For slices, you can use pattern matching; e.g.

let x: &[i32] = &[42];

if let [i] = x { // or equivalently `if let &[ref i] = x {`
    // now, i: &i32
}

and with let ... else syntax, you could eventually achieve panicking behavior in a syntactically straightforward manner like

let x: &[i32] = &[42];

let [i] = x else { panic!() };
// now, i: &i32

(playground)


For strings, pattern matching like this isn’t available. However for either case, you could use the Itertools crate for asserting iterators being of a small fixed length via Itertools::collect_tuple. This way you can write

use itertools::Itertools; // 0.10.3

fn main() {
    let x: &[i32] = &[42];

    let (i,) = x.iter().collect_tuple().unwrap();
    // now, i: &i32
    
    let s = "ß";
    let (c,) = s.chars().collect_tuple().unwrap();
    // now, c: char
}

(playground)


Regarding strings, checking for ā€œone charā€ is typically a problematic operation, since a single char in Rust really is just a single ā€œUnicode scalar valueā€, which has - in the general case [1] - little to do with most practical notions of ā€œa single characterā€ which will often be better represented by considering ā€œgrapheme clustersā€ instead..


  1. i.e. if you don’t artificially restrict yourself to just ASCII, or only composed/normalized characters ā†©ļøŽ

9 Likes

There's also Itertools::exactly_one for a more specific match.

11 Likes

There's also my old crate:

but I'd generally recommend using Itertools::exactly_one nowadays.

By the way this is doing nothing, matches! evaluates to a bool without side effects and by ignoring it you're making that line doing nothing. Thus your code reduces to "get the first element or panic, ignore if there are more".

8 Likes

There is an unstable assert_matches! for that though.

Here's some previous conversations about similar stuff that never landed:

2 Likes

What's the use-case for these?

There's a million things std could have, but saving a line or two sometimes is adding bloat for everyone. There's already a first() method, slice[0]. What's the value in having a shortcut for also asserting there are no more elements? Any why force it to panic?

There could be TryFrom for str to char conversion, but char is a misleading type (it's not a character). Unicode requires grapheme clusters to be represented as str.

I find this useful when doing things like parsing, e.g. when parsing cfg(not) it’s nice to check that there’s only one subcfg.

This I disagree with, it should return Option (which doesn’t let you distinguish between none or many, but you can use other APIs if you need to).