Atbash Cipher in Rust - Exercism exercise So I'm working on an implementation of the Atbash Cipher for Rust - it is an exercise on exercism.io. I come from a little C experience and found my code to be rather round about and kind of tortured. Doing str and String manipulation in Rust is something I haven't really grokked yet. It seems like this would take up fewer lines of code in C.

Below is my code - am I going about this in the right way for Rust, or am I missing some important concept or way of manipulating the data? Is this as simple as it should be?

The exercise involves getting an input &amp;str and outputting a String, with each character changed as per the Atbash cipher, adding a space every 5 characters. Included is also a decode function. This all goes in a lib.rs.

// "Encipher" with the Atbash cipher.
pub fn encode(plain: &amp;str) -&gt; String {
    let mut coded: String = plain.to_string();

    coded.retain(|c| c.is_ascii_alphanumeric());
    coded.make_ascii_lowercase();

    let coded_no_spacing = String::from_utf8(
        coded
            .bytes()
            .map(|c| {
                if c.is_ascii_alphabetic() {
                    122 - c + 97
                } else {
                    c
                }
            })
            .collect(),
    )
    .unwrap();

    spacer(coded_no_spacing)
}

/// "Decipher" with the Atbash cipher.
pub fn decode(cipher: &amp;str) -&gt; String {
    let mut out = encode(cipher);
    out.retain(|c| c.is_ascii_alphanumeric());
    out
}

fn spacer(coded_no_spacing: String) -&gt; String {
    let mut coded_no_spacing = coded_no_spacing.chars();

    let mut temp_char = coded_no_spacing.next();
    let mut counter = 0;
    let mut coded_with_spaces = "".to_string();
    while temp_char.is_some() {
        if counter % 5 == 0 &amp;&amp; counter != 0 {
            coded_with_spaces.push(' ');
        }
        coded_with_spaces.push(temp_char.unwrap());
        temp_char = coded_no_spacing.next();
        counter += 1;
    }
    coded_with_spaces
}
// "Encipher" with the Atbash cipher.
pub fn encode(plain: &amp;str) -&gt; String {
    let mut coded: String = plain.to_string();

    coded.retain(|c| c.is_ascii_alphanumeric());
    coded.make_ascii_lowercase();

    let coded_no_spacing = String::from_utf8(
        coded
            .bytes()
            .map(|c| {
                if c.is_ascii_alphabetic() {
                    122 - c + 97


You can b'z' and b'a' to refer to the ascii codes of the letters.

                } else {
                    c
                }
            })
            .collect(),
    )
    .unwrap();

    spacer(coded_no_spacing)
}


You are mixing two different approaches here. Firstly, you make a string out of the input and then modify it. Secondly, you use an iterator over the bytes of the string. This code would be more straightforward if you just iterated over the letters.

Here is my approach:

plain
    .chars()
    .filter_map(|c| {
        if c.is_ascii_alphabetic() {
            let letter = c.to_ascii_lowercase() as u8;
            Some(char::from(b'z' - letter + b'a'))
        } else if c.is_ascii_alphanumeric() {
            Some(c)
        } else {
            None
        }
    })
    .collect()


If you haven't seen it before, the filter_map function combines filtering and mapping. The closure can return either None, to remove the element or Some(x) to provide an element in the output.

/// "Decipher" with the Atbash cipher.
pub fn decode(cipher: &amp;str) -&gt; String {
    let mut out = encode(cipher);
    out.retain(|c| c.is_ascii_alphanumeric());
    out
}


It took me a bit to figure out why you were filtering the chars again. But I see it is remove the spacing. It would make more sense to split the basic ciphering and into its own function so you can call that without adding the spacing. Then you wouldn't have to filter it.

fn spacer(coded_no_spacing: String) -&gt; String {
    let mut coded_no_spacing = coded_no_spacing.chars();

    let mut temp_char = coded_no_spacing.next();
    let mut counter = 0;
    let mut coded_with_spaces = "".to_string();


I would use String::new() to create an empty string. For "extra credit", you could use String::with_capacity to reserve the correct amount of space for the string, something like:

String::with_capacity(coded_no_spacing.len() + coded_no_spacing.len() / 5);


That's dubiously worthwhile, but sometimes it can be a helpful optimization.

Onwards:

    while temp_char.is_some() {


Firstly, there is a construct you can use when you want to iterate as long as something return Some instead of None.

while let Some(temp_char) = coded_no_spacing.next()


But in this case, this is just iterating over for the chars, so you should use a forloop

for temp_char in coded_no_spacing.chars()

        if counter % 5 == 0 &amp;&amp; counter != 0 {
            coded_with_spaces.push(' ');
        }
        coded_with_spaces.push(temp_char.unwrap());
        temp_char = coded_no_spacing.next();
        counter += 1;


Instead of counting, use the enumerate() method on iterator. It will give you an index.

    }
    coded_with_spaces
}


Here is my version:

fn spacer(coded_no_spacing: &amp;str) -&gt; String {
    let mut coded_with_spaces = String::new();

    for (index, char) in coded_no_spacing.chars().enumerate() {
        if index % 5 == 0 &amp;&amp; index != 0 {
            coded_with_spaces.push(' ');
        }
        coded_with_spaces.push(char);
    }

    coded_with_spaces
}