Can this be done the un mutable way

Hello,

I try to solve a lot of challenges of exercism.io
and solve one like this :

pub fn raindrops(n: u32) -> String {
    let mut buffer = String::new();

    if n % 3 == 0 {
        buffer += "Pling";
    }

    if n % 5 == 0 {
        buffer += "Plang"
    }

    if n % 7 == 0 {
        buffer += "Plong"
    }

    if buffer.is_empty() {
        return n.to_string();
    }

    buffer
}

now because rust is immutable I wonder if I can make this code work without using mutable variables.

One way to eliminate mutability and make the code more robust is to introduce iterators.

pub fn raindrops(n: u32) -> String {
    let drops = [(3, "Pling"), (5, "Plang"), (7, "Plong")];
    
    let output = drops.iter()
        .filter_map(|&(num, sound)| {
            if n % num == 0 { Some(sound) } else { None }
        })
        .collect::<String>();
    
    if output.is_empty() {
        n.to_string()
    } else {
        output
    }
}
5 Likes

Deleted gibberish.

That doesn't do the same thing, for example with n = 15

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d2aa742dddc6e194a82e2486be59980b

Good point. I need my morning coffee.

both thanks. I will study both and try to figure out how they work so I learn something out of it

I think OP's original code is the most intuitive and ergonomic solution. Rust is a procedural, mutation based language because our computers works in this way. The difference from other mutation-based language is Rust makes mutation explicit and encourages to isolate it in language level, so there's no reason to avoid mutation if it compiles.

4 Likes

Ergonomics is the process of designing or arranging workplaces, products and systems so that they fit the people who use them.

The point about ergonomics is that humans are all different, one has to adapt things to fit the human and the job they are doing so that things work well and painlessly.

The original solution is OK if that's it. What if one expected to expand that from 3 tests to a thousand? That's a lot of "if"s to write. Then an imperative loop or functional iterator may be called for.

All depends...

2 Likes

yep, as always it depends. that is why I like to make software that can be changed easily because it seems that software always needs to be changed when used in real world cases

2 Likes

So there is not non mutable way to make this work ?

Sure you can:

pub fn raindrops(n: u32) -> String {
    
    let a = if n % 3 == 0 { "Pling" } else { "" };
    let b = if n % 5 == 0 { "Plang" } else { "" };
    let c = if n % 7 == 0 { "Plong" } else { "" };

    let result = String::new() + a + b + c;

    if result.len() == 0 {
        return n.to_string();
    }

    result
}
1 Like

You always can. There are plenty of functional language that have demonstrated that.

For example, here's an ugly-but-obvious translation to not use mutability:

pub fn raindrops(n: u32) -> String {
    fn a(n: u32) -> String {
        b(n, String::new())
    }
    fn b(n: u32, buffer: String) -> String {
        if n % 3 == 0 {
            c(n, buffer + "Pling")
        } else {
            c(n, buffer)
        }
    }
    fn c(n: u32, buffer: String) -> String {
        if n % 5 == 0 {
            d(n, buffer + "Plang")
        } else {
            d(n, buffer)
        }
    }
    fn d(n: u32, buffer: String) -> String {
        if n % 7 == 0 {
            e(n, buffer + "Plong")
        } else {
            e(n, buffer)
        }
    }
    fn e(n: u32, buffer: String) -> String {
        if buffer.is_empty() {
            n.to_string()
        } else {
            buffer
        }
    }

    a(n)
}