[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).

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