DEV Community

Cover image for 30 Days of Rust - Day 25
johnnylarner
johnnylarner

Posted on

30 Days of Rust - Day 25

What is up world, I promised you I'd be back again this week. Today's blog flog will shed some light on Rust's self-proclaimed functional aspects. I'm not really dedicated enough blog time to this topic, but I'm quite time-pressed this evening 🫣

Yesterday's questions answered

No questions to answer

Today's open questions

No open questions

5 days left until closure

Anonymous functions in Rust are known as closures. This feature of the language is particularly powerful due to the following fact:

Closures capture variables in the scope at which they are declared

We know that variable scope is fundamental point of thought when designing Rust code. Closures are the mechanism to get around some of the restrictions this poses. One obvious example of where this makes sense is creating a thread. You can make a variable available in that thread's scope.

Fn Traits

Closures (as well as normal functions) can implement so-called Fn Traits:

  • FnOnce -> Called once, moves captured data out when returning
  • FnMut -> Called more than once, may mutate captured values but won't move them
  • Fn -> Called more than once, no mutations, no captured values

These traits allow the Rust compiler to spot issues early. For example, if multiple threads call a closure that mutates a resource that all threads need access too. Similarly, if you move an item in a closure while its original owner is still active in scope, you'd encounter some nasty bugs (if the compiler let you).

Iterators are sexy

Turning collections into iterators opens up a bunch of useful operation to use as a developer. The map, filter and sum methods provide a readable, high-level way of writing code. Consider these two code snippets:

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    contents
        .lines()
        .filter(|line| line.contains(query))
        .collect()
}


pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    let mut results = Vec::new();

    for line in contents.lines() {
        if line.contains(query) {
            results.push(line)
        }
    } 

    results
}
Enter fullscreen mode Exit fullscreen mode

Not only is the first example shorter, the code is more expressive of the procedural nature of the task. What's more, we don't have to worry about state management (of the vector) which could become challenging in a multi-threaded system.

Top comments (0)