A functional way of doing this?

Is there a functional/idiomatic way of doing this? Essentially, this iteration is a bit tricky, because I want to:

  1. Iterate through a Vec<String>.
  2. For each element:
    • Split the string into components separated by periods;
    • Capitalize each component; and
    • Merge all the components back into a Pascal-cased string.
  3. Print each element.

I thought of making one iterator, and then in a map() closure I'd build yet another iterator, return that, and so on, but that seems cluttery and is quite confusing. I could always just use a standard for loop but I was wondering if this was a common enough thing that people had come up with a more idiomatic way of doing it already.

EDIT: Hey! Listen! This is a dangerous way to do it that can cause a panic! See the comment below! A functional way of doing this? - #3 by Hyeonu

The following seems to work, but maybe it's not so, as requested, idiomatic:

fn main() {
  let b = vec![
    "very.juicy.parenchyma".to_string(),
    "can.coagulate.rapidly".to_string(),
    "blocking.the.stromata".to_string()
  ];
  b.iter()
    .for_each(|s| {
        let p = s.split('.')
            .fold("".to_string(), |mut t, sub_s| {
                t.push_str(&sub_s[0..1].to_uppercase());
                t.push_str(&sub_s[1..]);
                t
            });
        println!("{}", p);
    });
}

It prints:

VeryJuicyParenchyma
CanCoagulateRapidly
BlockingTheStromata

playground

The for_each could be replaced with a for loop, too. This way makes it possible to chain into another iterator.

Constant indexing on UTF-8 string is dangerous - it may panic on non-ascii character. Consider some popular case conversion crate instead, like heck.

use heck::CamelCase; // `heck` calls it CamelCase - check its docs.

fn main() {
    let b = vec![
        "very.juicy.parenchyma".to_string(),
        "can.coagulate.rapidly".into(),
        "blocking.the.stromata".into(),
        "외국어.문자열".into(),
    ];

    for el in &b {
        let el: String = el.split('.')
             .map(str::to_camel_case)
             .collect();
        println!("{}", el);
    }
}
6 Likes

Sometimes purely-iterators-and-functional is less idiomatic, I find. If procedural or half-procedural is less clunky and less confusing, use that.

If you don't need to keep the Strings after printing, there's no reason to allocate them.

fn print_with_uppercased_first(s: &str) {
    let mut chars = s.chars();
    if let Some(c) = chars.next() {
        c.to_uppercase().for_each(|c| print!("{}", c));
        print!("{}", chars.as_str());
    }
}
// ...
    .for_each(|s| {
        s.split('.').for_each(print_with_uppercased_first);
        println!();
    });

Note that heck may capitalize things in the middle of your component if it sees a word boundary.

1 Like

FYI, the ToUppercase iterator implements Display itself, too. No need for the for_each :wink:

5 Likes

This worked. Not the most efficient option, but it does the job.

Could you clarify on this? How could constant indexing on a string panic on a non-ASCII character? Doesn't the Index trait implementation on Strings just call the String::get method?

fn main() {
    let s = "외국어.문자열";
    println!("{}", s.chars().next().unwrap().len_utf8());
    let _ = &s[0..1]; // panics
}

For example here, the first character is 3 bytes wide, not 1.

Since you talk about the get method, its documentation specifically references this.

Slicing is done on byte offsets and will panic if you slice in the middle of a multibyte UTF8 character encoding.

fn main() {
    let s = "외국어.문자열".to_string();
    println!("{}", &s[1..]);
}
// thread 'main' panicked at 'byte index 1 is not a char boundary;
//  it is inside '외' (bytes 0..3) of `외국어.문자열`', src/main.rs:3:21

Mentioned in the book but after looking around, I agree the documentation should be improved.

  • It more properly belongs on str which is in core and supplies the implementation
  • While you can't index with an integer, you can still slice, and this danger should be strongly called out
  • The reasoning against indexing with integers could also apply to slicing, which makes it rather weak

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.